Поделиться через


Определение пересечений в визуальном слое

В этом разделе представлен обзор функциональности проверки попаданий, предоставляемой визуальным уровнем. Поддержка тестирования попаданий позволяет определить, попадает ли геометрическое или точечное значение в пределах отрисованного содержимого "Visual", что позволяет реализовать функции пользовательского интерфейса, например прямоугольник выбора для выбора нескольких объектов.

Сценарии тестирования попаданий

Класс UIElement предоставляет метод InputHitTest, который позволяет тестировать элемент с использованием заданного значения координат. Во многих случаях метод InputHitTest предоставляет необходимые функциональные возможности для реализации тестирования попаданий элементов. Однако существует несколько сценариев, в которых может потребоваться реализовать тестирование попаданий на визуальном уровне.

  • Тестирование попаданий на объекты, отличные отUIElement. Это применимо к тестированию неUIElement объектов, таких как DrawingVisual или графические объекты.

  • Тестирование пересечений с использованием геометрии: это актуально, если необходимо использовать геометрический объект вместо координатной точки.

  • Тестирование попаданий по нескольким объектам: это применимо, если необходимо тестирование нескольких объектов, таких как перекрывающиеся объекты. Вы можете получить результаты для всех визуальных элементов, пересекающих геометрию или точку, а не только первую.

  • Игнорирование политики тестирования попаданий UIElement: это применимо, когда необходимо игнорировать политику тестирования попаданий UIElement, которая учитывает такие факторы, как то, отключен элемент или он невидим.

Заметка

Полный пример кода, демонстрирующий проверку пересечения на визуальном уровне, см. в Пример теста пересечения с использованием DrawingVisuals и Пример теста пересечения с использованием взаимодействия Win32.

Поддержка проверки на попадание

Назначение методов HitTest в классе VisualTreeHelper заключается в определении того, находится ли значение геометрии или координат точки в отрисованном содержимом заданного объекта, например элемента управления или графического элемента. Например, можно использовать тестирование попаданий, чтобы определить, попадает ли щелчок мыши в пределах ограничивающего прямоугольника объекта в геометрию круга. Вы также можете переопределить реализацию по умолчанию для тестирования попаданий для выполнения собственных пользовательских вычислений теста попаданий.

На следующем рисунке показана связь между областью не прямоугольного объекта и ограничивающим прямоугольником.

Диаграмма допустимого тестового региона попадания
Схема допустимого тестового региона попадания

Тестирование попаданий и Z-порядок

Визуальный слой Windows Presentation Foundation (WPF) поддерживает тестирование попаданий ко всем объектам в точке или геометрии, а не только к самому верхнему объекту. Результаты возвращаются в z-порядке. Однако визуальный объект, который передается в качестве параметра методу HitTest, определяет, какая часть визуального дерева будет проходить тест. Вы можете выполнить тестирование по всему визуальному дереву или любой ее части.

На следующем рисунке объект круга находится на вершине как квадратных, так и треугольниковых объектов. Если вы заинтересованы только в тестировании визуального объекта с наивысшим значением z-порядка, можно настроить перечисление визуальных тестов на попадание так, чтобы возвращалось Stop из HitTestResultCallback, чтобы остановить тестирование после первого элемента.

схема z-порядка визуального дерева
Схема z-порядка визуального дерева

Если вы хотите перечислить все визуальные объекты под определенной точкой или геометрией, верните Continue из HitTestResultCallback. Это означает, что вы можете проверить наличие визуальных объектов, находящихся под другими объектами, даже если они полностью скрыты. Дополнительные сведения см. в примере кода в разделе "Использование обратного вызова результатов теста попадания".

Заметка

Визуальный объект, который является прозрачным, также может быть протестирован.

Использование тестирования попаданий по умолчанию

Можно определить, находится ли точка в геометрии визуального объекта с помощью метода HitTest, чтобы указать визуальный объект и значение координат точки для проверки. Параметр визуального объекта определяет начальную точку в визуальном дереве для поиска теста попадания. Если визуальный объект найден в визуальном дереве, геометрия которого содержит координату, оно имеет свойство VisualHit объекта HitTestResult. Затем HitTestResult возвращается из метода HitTest. Если точка не содержится в визуальном поддереве, которое вы тестируете на попадание, HitTest возвращается null.

Заметка

Тестирование попаданий по умолчанию всегда возвращает самый верхний объект в z-порядке. Чтобы идентифицировать все визуальные объекты, даже те, которые могут быть частично или полностью скрыты, используйте обратный вызов результата теста на попадание.

Значение координаты, передаваемое в качестве параметра точки для метода HitTest, должно быть относительно пространства координат визуального объекта, на который выполняется тестирование. Например, если у вас есть вложенные визуальные объекты, определённые на координатах (100, 100) в пространстве родительского элемента, то тестирование на попадание в дочерний объект на координатах (0, 0) эквивалентно тестированию на (100, 100) в пространстве координат родителя.

В следующем коде показано, как настроить обработчики событий мыши для объекта UIElement, который используется для записи событий, используемых для тестирования попаданий.

// Respond to the left mouse button down event by initiating the hit test.
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // Retrieve the coordinate of the mouse position.
    Point pt = e.GetPosition((UIElement)sender);

    // Perform the hit test against a given portion of the visual object tree.
    HitTestResult result = VisualTreeHelper.HitTest(myCanvas, pt);

    if (result != null)
    {
        // Perform action on hit visual object.
    }
}
' Respond to the left mouse button down event by initiating the hit test.
Private Overloads Sub OnMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
    ' Retrieve the coordinate of the mouse position.
    Dim pt As Point = e.GetPosition(CType(sender, UIElement))

    ' Perform the hit test against a given portion of the visual object tree.
    Dim result As HitTestResult = VisualTreeHelper.HitTest(myCanvas, pt)

    If result IsNot Nothing Then
        ' Perform action on hit visual object.
    End If
End Sub

Как визуальное дерево влияет на определение попаданий

Начальная точка в визуальном дереве определяет, какие объекты возвращаются во время перечисления объектов теста попадания. Если у вас есть несколько объектов, которые нужно проверить, визуальный объект, используемый в качестве отправной точки в визуальном дереве, должен быть общим предком всех интересующих объектов. Например, если вы заинтересованы в тестировании элемента кнопки и визуального элемента на следующей схеме, необходимо задать начальную точку в визуальном дереве общим предком обоих. В этом случае элемент холста является общим предком элемента кнопки и визуального элемента рисования.

схема визуальной иерархии дерева
Схема иерархии визуального дерева

Заметка

Свойство IsHitTestVisible получает или задает значение, указывающее, может ли производный от UIElementобъект быть возвращён в качестве результата проверки на попадание из любой части отрисованного содержимого. Это позволяет выборочно изменять визуальное дерево, чтобы определить, какие визуальные объекты участвуют в тесте попадания.

Использование обратного вызова результата теста попадания

Можно перечислить все визуальные объекты в визуальном дереве, геометрия которого содержит указанное значение координаты. Это позволяет определить все визуальные объекты, даже те, которые могут быть частично или полностью скрыты другими визуальными объектами. Чтобы перечислить визуальные объекты в визуальном дереве, используйте метод HitTest с функцией обратного вызова нажатия. Система вызывает функцию обратного вызова теста попадания, когда заданное вами значение координат содержится в визуальном объекте.

Во время перечисления результатов теста попадания не следует выполнять операцию, которая изменяет визуальное дерево. Добавление или удаление объекта из визуального дерева во время обхода может привести к непредсказуемому поведению. После возврата метода HitTest можно безопасно изменить визуальное дерево. Возможно, потребуется предоставить структуру данных, такую как ArrayList, для хранения значений при перечислении результатов теста на попадание.

// Respond to the right mouse button down event by setting up a hit test results callback.
private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    // Retrieve the coordinate of the mouse position.
    Point pt = e.GetPosition((UIElement)sender);

    // Clear the contents of the list used for hit test results.
    hitResultsList.Clear();

    // Set up a callback to receive the hit test result enumeration.
    VisualTreeHelper.HitTest(myCanvas, null,
        new HitTestResultCallback(MyHitTestResult),
        new PointHitTestParameters(pt));

    // Perform actions on the hit test results list.
    if (hitResultsList.Count > 0)
    {
        Console.WriteLine("Number of Visuals Hit: " + hitResultsList.Count);
    }
}
' Respond to the right mouse button down event by setting up a hit test results callback.
Private Overloads Sub OnMouseRightButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
    ' Retrieve the coordinate of the mouse position.
    Dim pt As Point = e.GetPosition(CType(sender, UIElement))

    ' Clear the contents of the list used for hit test results.
    hitResultsList.Clear()

    ' Set up a callback to receive the hit test result enumeration.
    VisualTreeHelper.HitTest(myCanvas, Nothing, New HitTestResultCallback(AddressOf MyHitTestResult), New PointHitTestParameters(pt))

    ' Perform actions on the hit test results list.
    If hitResultsList.Count > 0 Then
        Console.WriteLine("Number of Visuals Hit: " & hitResultsList.Count)
    End If
End Sub

Метод обратного вызова при тесте на попадание определяет действия при обнаружении попадания на определенный визуальный объект в визуальном дереве. После выполнения действий возвращается значение HitTestResultBehavior, определяющее, следует ли продолжать перечисление других визуальных объектов.

// Return the result of the hit test to the callback.
public HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
    // Add the hit test result to the list that will be processed after the enumeration.
    hitResultsList.Add(result.VisualHit);

    // Set the behavior to return visuals at all z-order levels.
    return HitTestResultBehavior.Continue;
}
' Return the result of the hit test to the callback.
Public Function MyHitTestResult(ByVal result As HitTestResult) As HitTestResultBehavior
    ' Add the hit test result to the list that will be processed after the enumeration.
    hitResultsList.Add(result.VisualHit)

    ' Set the behavior to return visuals at all z-order levels.
    Return HitTestResultBehavior.Continue
End Function

Заметка

Порядок перечисления визуальных объектов в порядке обнаружения соответствует z-порядку. Визуальный объект на верхнем уровне z-порядка является первым перечисленным объектом. Все остальные визуальные объекты, перечисленные, находятся на убывающем уровне z-порядка. Этот порядок перечисления соответствует порядку отрисовки визуальных элементов.

Перечисление визуальных объектов можно в любое время остановить в функции обратного вызова теста попадания, возвращая Stop.

// Set the behavior to stop enumerating visuals.
return HitTestResultBehavior.Stop;
' Set the behavior to stop enumerating visuals.
Return HitTestResultBehavior.Stop

Использование обратного вызова фильтра проверки на попадание

Можно использовать необязательный фильтр для проверки попадания, чтобы ограничить объекты, передаваемые в результаты теста на попадание. Это позволяет вам игнорировать части визуального дерева, в обработке которых вы не заинтересованы в ваших результатах теста на попадание. Чтобы реализовать фильтр теста попадания, необходимо определить функцию обратного вызова фильтра попадания и передать ее в качестве значения параметра при вызове метода HitTest.

// Respond to the mouse wheel event by setting up a hit test filter and results enumeration.
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
    // Retrieve the coordinate of the mouse position.
    Point pt = e.GetPosition((UIElement)sender);

    // Clear the contents of the list used for hit test results.
    hitResultsList.Clear();

    // Set up a callback to receive the hit test result enumeration.
    VisualTreeHelper.HitTest(myCanvas,
                      new HitTestFilterCallback(MyHitTestFilter),
                      new HitTestResultCallback(MyHitTestResult),
                      new PointHitTestParameters(pt));

    // Perform actions on the hit test results list.
    if (hitResultsList.Count > 0)
    {
        ProcessHitTestResultsList();
    }
}
' Respond to the mouse wheel event by setting up a hit test filter and results enumeration.
Private Overloads Sub OnMouseWheel(ByVal sender As Object, ByVal e As MouseWheelEventArgs)
    ' Retrieve the coordinate of the mouse position.
    Dim pt As Point = e.GetPosition(CType(sender, UIElement))

    ' Clear the contents of the list used for hit test results.
    hitResultsList.Clear()

    ' Set up a callback to receive the hit test result enumeration.
    VisualTreeHelper.HitTest(myCanvas, New HitTestFilterCallback(AddressOf MyHitTestFilter), New HitTestResultCallback(AddressOf MyHitTestResult), New PointHitTestParameters(pt))

    ' Perform actions on the hit test results list.
    If hitResultsList.Count > 0 Then
        ProcessHitTestResultsList()
    End If
End Sub

Если вы не хотите предоставлять необязательную функцию обратного вызова фильтра для теста на попадание, передайте значение null в качестве параметра для метода HitTest.

// Set up a callback to receive the hit test result enumeration,
// but no hit test filter enumeration.
VisualTreeHelper.HitTest(myCanvas,
                  null,  // No hit test filtering.
                  new HitTestResultCallback(MyHitTestResult),
                  new PointHitTestParameters(pt));
' Set up a callback to receive the hit test result enumeration,
' but no hit test filter enumeration.
VisualTreeHelper.HitTest(myCanvas, Nothing, New HitTestResultCallback(AddressOf MyHitTestResult), New PointHitTestParameters(pt)) ' No hit test filtering.

обрезание визуального дерева с помощью фильтра теста попадания
Обрезка визуального дерева

Функция обратного вызова фильтра теста на пересечение позволяет перечислять все визуальные элементы, отображаемое содержимое которых включает указанные координаты. Однако вы можете захотеть игнорировать определенные ветви визуального дерева, которые вам неинтересны для обработки в вашей функции обратного вызова результатов теста попадания. Возвращаемое значение функции обратного вызова фильтра тестового попадания определяет, какие действия должны быть предприняты при перечислении визуальных объектов. Например, если вы возвращаете значение, ContinueSkipSelfAndChildren, можно удалить текущий визуальный объект и его дочерние элементы из перечисления результатов теста попадания. Это означает, что функция обратного вызова результатов теста попадания не увидит эти объекты в его перечислении. Очистка визуального дерева объектов уменьшает объем обработки во время прохождения перечисления результатов теста попадания. В следующем примере кода фильтр пропускает метки и их потомков, а тестирует все остальное.

// Filter the hit test values for each object in the enumeration.
public HitTestFilterBehavior MyHitTestFilter(DependencyObject o)
{
    // Test for the object value you want to filter.
    if (o.GetType() == typeof(Label))
    {
        // Visual object and descendants are NOT part of hit test results enumeration.
        return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
    }
    else
    {
        // Visual object is part of hit test results enumeration.
        return HitTestFilterBehavior.Continue;
    }
}
' Filter the hit test values for each object in the enumeration.
Public Function MyHitTestFilter(ByVal o As DependencyObject) As HitTestFilterBehavior
    ' Test for the object value you want to filter.
    If o.GetType() Is GetType(Label) Then
        ' Visual object and descendants are NOT part of hit test results enumeration.
        Return HitTestFilterBehavior.ContinueSkipSelfAndChildren
    Else
        ' Visual object is part of hit test results enumeration.
        Return HitTestFilterBehavior.Continue
    End If
End Function

Заметка

Обратный вызов фильтра теста попадания иногда вызывается в тех случаях, когда обратный вызов результатов теста не вызывается.

Переопределение тестирования попаданий по умолчанию

Вы можете переопределить поддержку тестирования попаданий визуального объекта по умолчанию, переопределив метод HitTestCore. Это означает, что при вызове метода HitTest вызывается переопределенная реализация HitTestCore. Переопределенный метод вызывается, когда во время теста на попадание координата попадает в ограничивающий прямоугольник визуального объекта, даже если она выходит за пределы отображаемого содержимого этого объекта.

// Override default hit test support in visual object.
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
    Point pt = hitTestParameters.HitPoint;

    // Perform custom actions during the hit test processing,
    // which may include verifying that the point actually
    // falls within the rendered content of the visual.

    // Return hit on bounding rectangle of visual object.
    return new PointHitTestResult(this, pt);
}
' Override default hit test support in visual object.
Protected Overrides Overloads Function HitTestCore(ByVal hitTestParameters As PointHitTestParameters) As HitTestResult
    Dim pt As Point = hitTestParameters.HitPoint

    ' Perform custom actions during the hit test processing,
    ' which may include verifying that the point actually
    ' falls within the rendered content of the visual.

    ' Return hit on bounding rectangle of visual object.
    Return New PointHitTestResult(Me, pt)
End Function

Иногда могут возникнуть случаи, когда требуется выполнить тестирование как ограничивающего прямоугольника, так и отрисованного содержимого визуального объекта. Используя значение параметра PointHitTestParameters в переопределенном методе HitTestCore в качестве параметра базового метода HitTestCore, вы можете выполнять действия, исходя из попадания в ограничивающий прямоугольник визуального объекта, а затем выполнить второй тест попадания на отрисованное содержимое визуального объекта.

// Override default hit test support in visual object.
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
    // Perform actions based on hit test of bounding rectangle.
    // ...

    // Return results of base class hit testing,
    // which only returns hit on the geometry of visual objects.
    return base.HitTestCore(hitTestParameters);
}
' Override default hit test support in visual object.
Protected Overrides Overloads Function HitTestCore(ByVal hitTestParameters As PointHitTestParameters) As HitTestResult
    ' Perform actions based on hit test of bounding rectangle.
    ' ...

    ' Return results of base class hit testing,
    ' which only returns hit on the geometry of visual objects.
    Return MyBase.HitTestCore(hitTestParameters)
End Function

См. также