Hit testing child controls added to a canvas does

2019-07-16 13:51发布

问题:

I am trying to do hit testing on a collection of user controls added to a canvas at runtime.

My canvas:

<Canvas x:Name="LayoutRoot" Background="White">
    <Canvas  x:Name="Carrier" Canvas.ZIndex="-1">
    </Canvas>
</Canvas>

My canvas code:

    public MainPage()
    {
        InitializeComponent();

        var uiElement = new MyUserControl();
        this.Carrier.Children.Add(uiElement);

        MouseLeftButtonDown += MouseLeftButtonDownHandler;
    }

    private List<UIElement> HitSprite(Point p)
    {
        var hitElements = 
           VisualTreeHelper.FindElementsInHostCoordinates(p, this.Carrier) as List<UIElement>;

        return hitElements.Count > 0 ? hitElements : null;
    }

    private void MouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e)
    {
        var list = HitSprite(e.GetPosition(this.LayoutRoot));
    }

My user control:

<Canvas x:Name="Container" AllowDrop="True">
    <StackPanel Name="Description" Height="65" Canvas.ZIndex="2" IsHitTestVisible="False">
        <TextBlock Text="Testing" x:Name="Name" FontSize="12" FontWeight="Bold" HorizontalAlignment="Center" IsHitTestVisible="False"/>

        <Border Background="Green">
            <Image x:Name="Body" Stretch="None" Canvas.ZIndex="0" Height="20" Width="20">
            </Image>
        </Border>
    </StackPanel>
</Canvas>

I've tried various combinations of Carrier and LayoutRoot for GetPoint and FindElementsInHostCoordinates but the elements returned are always empty.

What am I doing wrong?

回答1:

There are a couple of things you have to be aware of about hit testing:

  1. Elements without a background won't appear in the hit test, so set the background, even setting it to transparent work. This happens because objects need to be "solid" for hit testing.
  2. Elements without a width and height won't appear in the hit test, because they do not have a body to hit.
  3. Elements where IsHitTestVisible=false won't appear in the hit test, they have been explicitly excluded from it.

In your case, a combination of this factors was making your user control invisible to hit test:

  1. Your user control is not solid, it does not have a background.
  2. The canvas that serves as layout root in your user control does not have a size set, and therefore width and height are zero. It does not have a background either.
  3. All children elements in your user control have IsHitTestVisible=false, which exclude them from the hit test explicitly.

I made a couple of modifications to your user control XAML and the code behind and I see your user control in the hit test result now:

    public MainPage()
    {
        // Required to initialize variables
        InitializeComponent();

        var uiElement = new MyUserControl();
        this.Carrier.Children.Add(uiElement);

        MouseLeftButtonDown += MouseLeftButtonDownHandler;
    }
    private List<UIElement> HitSprite(Point p)
    {
        var hitElements =
           VisualTreeHelper.FindElementsInHostCoordinates(p, this.LayoutRoot) as List<UIElement>;

        return hitElements.Count > 0 ? hitElements : null;
    }

    private void MouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e)
    {
        var list = HitSprite(e.GetPosition(this.LayoutRoot));
    }

XAML:

<Grid x:Name="Container" AllowDrop="True" Background="Transparent">
    <StackPanel Name="Description" Height="65" Canvas.ZIndex="2" IsHitTestVisible="False">
        <TextBlock Text="Testing" x:Name="Name" FontSize="12" FontWeight="Bold" HorizontalAlignment="Center" IsHitTestVisible="False"/>

        <Border Background="Green">
            <Image x:Name="Body" Stretch="None" Canvas.ZIndex="0" Height="20" Width="20">
            </Image>
        </Border>
    </StackPanel>
</Grid>

As an additional advice, it is not a good idea to nest canvas panels unless there is a specific reason to do so. Canvas is a special panel in many ways, for example, it won't clip the items that go outside its bounds, it does not have a size by default unless you set one and it is not a solid object unless you set a background explicitly. Try to use Grid and StackPanel to arrange objects and use the canvas for specific positioning on (x,y) coordinates only.



回答2:

Have you checked out UIElement.InputHitTest? might be woth a try if you're having trouble with visualtreehelper

-edit-

As an aside, you should probably use the same element in the e.GetPosition() call as you're using in the VisualTreeHelper.FindElementsInHostCoordinates() call, but you've tried that i suppose?