0

I am doing some experiments and I am trying to collect all elements which are placed under mouse pointer.

XAML test code

<StackPanel>
    <Button
        x:Name="HitButton"
        Content="Test Button" />
    <Button
        x:Name="NotHitButton"
        IsHitTestVisible="False"
        Content="Test Button" />
</StackPanel>

To get coordinates of mouse pointer I use PointerMoved:

CoreWindow.GetForCurrentThread().PointerMoved += OnPointerMoved;

private void OnPointerMoved(CoreWindow sender, PointerEventArgs args)
{
    Point point = args.CurrentPoint.Position;
    IEnumerable<UIElement> elements = VisualTreeHelper.FindElementsInHostCoordinates(point, null, true);
    ...
}

This approach works, however, when a control has property IsHitTestVisible set up to false, FindElementsInHostCoordinates does not consider it even if I set up the third parameter includeAllElements of this method to true.

According to documentation I would expect if I set the parameter includeAllElements to true, it should find all elements, including those controls which have IsHitTestVisible set up to false.

includeAllElements [System.Boolean]

true to include all elements that intersect, including those elements considered to be invisible to hit testing. false to find only visible, hit-testable elements. The default is false.

Do I understand wrong way how FindElementsInHostCoordinates works? If so, is there any other way how to retrieve all controls at specific coordinates even if they have IsHitTestVisible set to false?

Jakub Krampl
  • 1,774
  • 1
  • 16
  • 24

2 Answers2

1

@ZORRO's observation is mostly correct.

XAML hit-testing is based on the philosophy that if an element occupies space on screen and "produces ink" then it hit-tests.

The main question is around what produces ink:

  • For elements that have a Brush (the vast majority), any non-null brush is considered something that produces ink, even if the brush doesn't produce visible pixels. For example a SolidColorBrush with color "Transparent" still produces ink. Only a null brush does not produce ink.

  • There are some special elements such as SwapChainPanel and MediaElement that don't have a brush but can still produce ink.

  • The Opacity property is not considered here

So as long as one of the above is true, the element still produces ink even if Opacity equals 0.

What all this means is when the includeAllElements parameter is set to true, elements that don't produce ink are considered for hit-testing. In this case, as long as the element meets the spatial requirements (point/rect intersects the element bounds), then it and its ancestors are included in the results.

BTW, we are working with content team to refine this document to make it clear.

Community
  • 1
  • 1
Franklin Chen - MSFT
  • 4,845
  • 2
  • 17
  • 28
0

I'd think the key point of this question is what are the "elements considered to be invisible to hit testing".

I'd think when we set IsHitTestVisible to false, the element is not considered to be invisible, it's just invisible. And according to Hit testing and input events, the element meets following criteria is hit-testable.

  • The element's Visibility property value is Visible.
  • The element's Background or Fill property value is not null. A null Brush value results in transparency and hit test invisibility. (To make an element transparent but also hit testable, use a Transparent brush instead of null.)
  • If the element is a control, its IsEnabled property value must be true.
  • The element must have actual dimensions in layout. An element where either ActualHeight and ActualWidth are 0 won't fire input events.

So I think when a element's Visibility property value is Collapsed or its Background or Fill property value is null or its IsEnabled property value is false, the element is considered to be invisible. And I did a simple test.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Border Name="Border" Width="200" Height="300" BorderThickness="1" BorderBrush="Black" Background="AliceBlue">
        <Grid Name="Grid" Width="100" Height="250">
            <Rectangle Name="CollapsedRectangle" Fill="Azure" Visibility="Collapsed" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" />
            <Rectangle Name="NoFillRectangle" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,100,0,0" />
            <Button Name="Button" IsEnabled="False" VerticalAlignment="Top" Margin="0,200,0,0">CLICK</Button>
        </Grid>
    </Border>
</Grid>

public MainPage()
{
    this.InitializeComponent();
    CoreWindow.GetForCurrentThread().PointerMoved += (s, e) =>
    {
        Point point = e.CurrentPoint.Position;
        IEnumerable<UIElement> elements = VisualTreeHelper.FindElementsInHostCoordinates(point, Border, false);

        foreach (var item in elements)
        {
            FrameworkElement feItem = item as FrameworkElement;
            System.Diagnostics.Debug.WriteLine(feItem.Name);
        }

        System.Diagnostics.Debug.WriteLine("-----------------------------------------");
    };
}

When includeAllElements parameter is set to false, the output is like

Border
-----------------------------------------
Border
-----------------------------------------
Border
-----------------------------------------

And when includeAllElements parameter is set to true, the output is like

Border
-----------------------------------------
Grid
Border
-----------------------------------------
...
NoFillRectangle
Grid
Border
-----------------------------------------
...
Grid
Border
-----------------------------------------
Border
-----------------------------------------

So it seems that includeAllElements parameter only takes effect when  element's Background or Fill property value is null. Not sure if this is by design. Maybe only the elements whose Background or Fill property value is null is considered to be invisible.

ZORRO
  • 528
  • 3
  • 13
  • That is very interesting research, thanks a lot. Do you happen to know if there is any alternative approach how to find all elements in a position besides `FindElementsInHostCoordinates` which could find really all elements without any obstacles? – Jakub Krampl Feb 20 '17 at 10:03