Compartir a través de


Pruebas de detección de colisiones en la capa visual

En este tema se proporciona una visión general sobre la funcionalidad de pruebas de impacto ofrecida por la capa visual. La compatibilidad con las pruebas de posicionamiento permite determinar si un valor de geometría o punto está dentro del contenido renderizado de un Visual, lo que le permite implementar el comportamiento de la interfaz de usuario, como un rectángulo de selección para seleccionar varios objetos.

Escenarios de pruebas de detección de área de impacto

La clase UIElement proporciona el método InputHitTest, que permite realizar pruebas de impacto en un elemento mediante un valor de coordenada determinado. En muchos casos, el InputHitTest método proporciona la funcionalidad deseada para implementar pruebas de impacto de elementos. Sin embargo, hay varios escenarios en los que tenga que implementar determinación de impactos en la capa visual.

  • Pruebas de impacto en objetos que no son de tipo UIElement: Esto se aplica si está probando objetos que no son de tipo UIElement, como DrawingVisual o objetos gráficos.

  • Pruebas de posicionamiento mediante una geometría: esto aplica cuando se necesita realizar una prueba de posicionamiento mediante un objeto de geometría en lugar del valor de coordenada de un punto.

  • Pruebas de colisión con múltiples objetos: esto se aplica cuando se necesita detectar colisiones en múltiples objetos, como objetos superpuestos. Puede obtener resultados para todos los objetos visuales que intersecan una geometría o un punto, no solo el primero.

  • Omitir la directiva de UIElement pruebas de posicionamiento: esto se aplica cuando se necesita omitir la UIElement directiva de pruebas de posicionamiento, que tiene en cuenta factores como si un elemento está deshabilitado o invisible.

Nota:

Para obtener un ejemplo de código completo que ilustra las pruebas de impacto en la capa visual, consulte Hit Test Using DrawingVisuals Sample y Hit Test with Win32 Interoperation Sample.

Compatibilidad con pruebas de detección de impacto

El propósito de los HitTest métodos de la VisualTreeHelper clase es determinar si un valor de coordenada de geometría o punto está dentro del contenido representado de un objeto determinado, como un control o un elemento gráfico. Por ejemplo, puede usar pruebas de posicionamiento para determinar si un clic del mouse dentro del rectángulo delimitador de un objeto se encuentra dentro de la geometría de un círculo. También puede optar por sobrescribir la implementación predeterminada de las pruebas de posicionamiento para realizar sus propios cálculos de pruebas de posicionamiento personalizados.

En la ilustración siguiente se muestra la relación entre la región de un objeto no rectangular y su rectángulo delimitador.

Diagrama de la región de prueba de impacto válida
Diagrama de la región válida de prueba de impacto

Pruebas de impacto y orden Z

La capa visual de Windows Presentation Foundation (WPF) admite la detección de impactos contra todos los objetos que se encuentren en un punto o dentro de una geometría, no solo en el objeto superior. Los resultados se devuelven en orden z. Sin embargo, el objeto visual que se pasa como parámetro al método HitTest determina la parte del árbol visual que será sometida a la prueba de impacto. Puede realizar una prueba de colisión en todo el árbol visual o en cualquier parte de él.

En la ilustración siguiente, el objeto círculo está sobre el objeto cuadrado y el objeto triángulo. Si solo está interesado en las pruebas de impacto del objeto visual cuyo valor de orden z es el más alto, puede establecer la enumeración de prueba de impacto visual para que devuelva Stop desde HitTestResultCallback y de esta manera detener el recorrido de la prueba de impacto después del primer elemento.

Diagrama del orden z de un árbol visual
Diagrama del orden z de un árbol visual

Si desea enumerar todos los objetos visuales bajo un punto o geometría específica, devuelva Continue desde HitTestResultCallback. Esto significa que puede realizar pruebas de colisión para objetos visuales que están debajo de otros objetos, incluso si están totalmente ocultos. Consulte el código de ejemplo en la sección "Usar una devolución de llamada para los resultados de pruebas de impacto" para obtener más información.

Nota:

Un objeto visual que es transparente también se puede someter a un test de colisión.

Uso de pruebas de impacto predeterminadas

Puede identificar si un punto está dentro de la geometría de un objeto visual mediante el HitTest método para especificar un objeto visual y un valor de coordenada de punto con el que probar. El parámetro del objeto visual identifica el punto de inicio del árbol visual para la búsqueda de pruebas de impacto. Si se encuentra un elemento visual en el árbol visual cuya geometría contiene la coordenada, se asigna a la propiedad VisualHit de un objeto HitTestResult. Luego, se devuelve el HitTestResult desde el método HitTest. Si el punto no está incluido en el subárbol visual que está probando, HitTest devuelve null.

Nota:

Las pruebas de posicionamiento predeterminadas siempre devuelven el objeto más alto en el orden z. Para identificar todos los objetos visuales, incluso aquellos que pueden estar parcialmente o totalmente ocultos, use un callback de resultado de prueba de impacto.

El valor de coordenada que se pasa como parámetro de punto en el método HitTest debe ser relativo al espacio de coordenadas del objeto visual con el que se realiza la prueba de impacto visual. Por ejemplo, si tiene objetos visuales anidados definidos en (100, 100) en el espacio de coordenadas del elemento principal, la prueba de impacto de un visual secundario en (0, 0) equivale a la prueba de impacto en (100, 100) en el espacio de coordenadas del elemento principal.

En el siguiente código se muestra cómo configurar controladores de eventos del ratón para un UIElement objeto que se utiliza para capturar eventos empleados en la detección de impactos.

// 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

Cómo afecta el árbol visual a las pruebas de posicionamiento

El punto inicial del árbol visual determina qué objetos se devuelven durante la enumeración de objetos de la prueba de posicionamiento. Si tiene varios objetos que desea probar, el objeto visual usado como punto de partida en el árbol visual debe ser el antecesor común de todos los objetos de interés. Por ejemplo, si estuviera interesado en pruebas de detección de impacto en tanto el elemento de botón como el dibujo visual en el diagrama siguiente, tendría que establecer el punto de partida en el árbol visual en el ancestro común de ambos. En este caso, el elemento canvas es el antecesor común del elemento de botón y del objeto visual de dibujo.

Diagrama de una jerarquía de árbol visual
Diagrama de una jerarquía de árboles visuales

Nota:

La propiedad IsHitTestVisible obtiene o establece un valor que declara si un objeto derivado de UIElement puede ser devuelto como resultado de una prueba de impacto de alguna parte de su contenido representado. Esto le permite alterar el árbol visual selectivamente para determinar qué objetos visuales intervienen en una prueba de impacto.

Uso de un Callback de Resultado de Hit Test

Puede enumerar todos los objetos visuales de un árbol visual cuya geometría contiene un valor de coordenada especificado. Esto le permite identificar todos los objetos visuales, incluso aquellos que pueden estar parcialmente o totalmente ocultos por otros objetos visuales. Para enumerar objetos visuales en un árbol visual, use el método HitTest con una función de devolución de llamada de prueba de impacto. El sistema llama a la función de devolución de llamada de la prueba de impacto cuando el valor de coordenadas que especifiques se encuentra en un objeto visual.

Durante la enumeración de resultados de la prueba de impacto, no debe realizar ninguna operación que modifique el árbol visual. Agregar o quitar un objeto del árbol visual mientras se recorre puede provocar un comportamiento impredecible. Puede modificar de forma segura el árbol visual después de que el método HitTest devuelva. Es posible que desee proporcionar una estructura de datos, como un ArrayList, para almacenar valores durante la enumeración de resultados de la prueba de impacto.

// 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

El método de callback de prueba de impacto define las acciones que se realizan cuando se identifica una prueba de impacto sobre un objeto visual específico dentro del árbol visual. Después de realizar las acciones, se devuelve un HitTestResultBehavior valor que determina si se va a continuar la enumeración de cualquier otro objeto visual o no.

// 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

Nota:

El orden de enumeración de los objetos visuales impactados es por orden Z. El objeto visual en el nivel de orden z superior es el primer objeto enumerado. Cualquier otro objeto visual enumerado se encuentra en el nivel de orden z decreciente. Este orden de enumeración corresponde al orden de representación de los objetos visuales.

Puede detener en cualquier momento la enumeración de objetos visuales en la función de devolución de llamada de prueba de posicionamiento devolviendo Stop.

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

Uso de un callback de filtro de Hit Test

Puede usar un filtro de prueba de impacto opcional para restringir los objetos que se pasan a los resultados de la prueba de impacto. Esto le permite omitir partes del árbol visual que no le interesa procesar en los resultados de la prueba de posicionamiento. Para implementar un filtro de prueba de impacto, defina una función de devolución de llamada para el filtro de prueba de impacto y pásela como un valor de parámetro al método 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

Si no desea proporcionar la función opcional de devolución de llamada para el filtro de prueba de impacto, pase un valor null como parámetro para el método 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.

Eliminación de un árbol visual mediante un filtro de prueba de posicionamiento
Eliminación de un árbol visual

La función de devolución de llamada del filtro de prueba de impacto permite enumerar todos los elementos visuales cuyo contenido renderizado contiene las coordenadas que usted especifique. Sin embargo, es posible que desee omitir ciertas ramas del árbol visual que no le interesen procesar en la función de llamada de retorno de los resultados de pruebas de impacto. El valor devuelto por la función de devolución de llamada del filtro de prueba de impacto determina qué tipo de acción debe realizar la enumeración de los objetos visuales. Por ejemplo, si devuelve el valor ContinueSkipSelfAndChildren, puede quitar el objeto visual actual y sus elementos secundarios de la enumeración de resultados de la prueba de colisión. Esto significa que la función de devolución de llamada de resultados de la prueba de posicionamiento no verá estos objetos en su enumeración. La eliminación del árbol visual de objetos reduce la cantidad de procesamiento durante el paso de enumeración de resultados de pruebas de posicionamiento. En el ejemplo de código siguiente, el filtro omite las etiquetas y sus descendientes y realiza pruebas de impacto en todo lo demás.

// 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

Nota:

A veces, se llamará a la devolución de llamada del filtro de prueba de impacto en los casos en los que no se llame a la devolución de llamada de los resultados de la prueba de impacto.

Invalidación de las pruebas de impacto predeterminadas

Puede sobrescribir la compatibilidad predeterminada con las pruebas de posicionamiento de un objeto visual sobrescribiendo el método HitTestCore. Esto significa que cuando se invoca el HitTest método , se llama a la implementación invalidada de HitTestCore . Se llama al método invalidado cuando una prueba de posicionamiento se encuentra dentro del rectángulo delimitador del objeto visual, incluso si la coordenada está fuera del contenido representado del objeto visual.

// 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

Puede haber ocasiones en las que quiera hacer pruebas de detección tanto en el rectángulo delimitador como en el contenido renderizado de un objeto visual. Mediante el uso del valor del parámetro en su método PointHitTestParameters invalidado HitTestCore, como parámetro para el método base HitTestCore, puede realizar acciones basadas en un impacto del rectángulo delimitador de un objeto visual y luego realizar una segunda prueba de impacto contra el contenido representado del objeto visual.

// 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

Consulte también