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?
There are a couple of things you have to be aware of about hit testing:
- 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.
- Elements without a width and height won't appear in the hit test, because they do not have a body to hit.
- 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:
- Your user control is not solid, it does not have a background.
- 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.
- 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.
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?