Странное поведение FindElementsInHostCoordinates в WinRT



Есть в Silverlight отличный метод: VisualTreeHelper.FindElementsInHostCoordinates — позволяет выполнять HitTest, т.е. для некоторой точки или прямоугольника искать все объекты визуального поддерева, которые с этими точкой или прямоугольником пересекаются. Внешне точно такой же метод VisualTreeHelper.FindElementsInHostCoordinates можно встретить в WinRT. И вроде выглядит-то он точно также, но есть нюанс: работает этот чудо-метод в разных версиях платформы по-разному. Давайте разберёмся.

Сначала создадим простое Silverlight 5 приложение. Основная вёрстка будет выглядеть следующим образом:

<Grid x:Name="LayoutRoot">
  <Grid.RowDefinitions>
    <RowDefinition Height="*"/>
    <RowDefinition Height="100"/>
  </Grid.RowDefinitions>

  <Canvas MouseLeftButtonDown="OnMainCanvasMouseLeftButtonDown" 
          x:Name="MainCanvas" Background="LightGreen">
    <Ellipse Width="200" Height="200" Fill="LightCoral" />
    <Path Fill="LightBlue" Data="M 10,100 C 10,300 300,-200 300,100"/>
  </Canvas>

  <TextBlock Grid.Row="1" x:Name="StatusBlock" />
</Grid>

Тут всё очень просто: имеется Canvas, а на нём лежит Ellipse и Path. Прямо под этим чудесным произведением искусства находится TextBlock в который мы можем вывести что-нибудь полезное. Выглядит приложение следующим образом:

Теперь напишем обработчик события для клика мышкой по нашему Canvas-элементу: будем искать элементы, в которые мы попали. Для этого нам пригодятся две версии VisualTreeHelper.FindElementsInHostCoordinates (для точки и для прямоугольника):

public static IEnumerable FindElementsInHostCoordinates(
 Point intersectingPoint,
 UIElement subtree
)
public static IEnumerable FindElementsInHostCoordinates(
 Rect intersectingRect,
 UIElement subtree
)

Список полученных объектов будем выводить в StatusBlock:

private void OnMainCanvasMouseLeftButtonDown(object sender, 
                                             MouseButtonEventArgs e)
{
  var p = e.GetPosition(MainCanvas);
  var listPoint = VisualTreeHelper.FindElementsInHostCoordinates(
                    new Point(p.X, p.Y), MainCanvas).ToList();
  var listRect = VisualTreeHelper.FindElementsInHostCoordinates(
                    new Rect(p.X, p.Y, 1, 1), MainCanvas).ToList();
  var strPoint = string.Join(", ", 
                   listPoint.Select(el => el.GetType().Name.ToString()));
  var strRect = string.Join(", ", 
                   listRect.Select(el => el.GetType().Name.ToString()));
  StatusBlock.Text = string.Format("[{0}] vs [{1}]", strPoint, strRect);
}

Наше отличное приложение готово! Что-то внутри подсказывает, что ввиду размеров прямоугольника (1x1) результаты работы двух перегрузок метода не должны отличаться. Давайте проверим, потыкав в разные места. Следующая картинка показывает результаты проведённого опыта:

Ну, вроде всё хорошо, методы отработали, как и ожидалось. А теперь перейдём к WinRT. Создадим новое Windows Store приложение и снабдим его аналогичной вёрсткой:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
  <Grid.RowDefinitions>
    <RowDefinition Height="*"/>
    <RowDefinition Height="100"/>
  </Grid.RowDefinitions>

  <Canvas Tapped="OnMainCanvasTapped" 
          x:Name="MainCanvas" Background="LightGreen">
    <Ellipse Width="200" Height="200" Fill="LightCoral" />
    <Path Fill="LightBlue" Data="M 10,100 C 10,300 300,-200 300,100"/>
  </Canvas>

  <TextBlock Grid.Row="1" x:Name="StatusBlock" />
</Grid>

Код обработчика OnMainCanvasTapped полностью совпадает с кодом OnMainCanvasMouseLeftButtonDown. Давайте запустим приложение и потыкаем в него. Результаты:

Вот это поворот! Недолгое кликанье по приложению быстро подведёт нас к выводу: точечный HitTest работает точно также, как и в Silverlight, а вот HitTest по прямоугольнику для Path-фигур работает не по самой фигуре, а по её BoundingBox-у (ограничивающему прямоугольнику). WinRT-приложения делаются по принципу Touch First, так что наиболее интересна именно Rect-версия метода. В большинстве случаев этот момент скорее всего будет не особо принципиален, но вот если приложение ориентировано на взаимодействие с различными изогнутыми элементами, то на особенность такого поведения FindElementsInHostCoordinates лучше бы обратить особое внимание.

Поделиться:
Исходный код поста находится на GitHub:
https://github.com/AndreyAkinshin/aakinshin.net/blob/master/ru/_posts/dotnet/findelementsinhostcoordinates.md