Realizar pruebas de posicionamiento en la capa visual

En este tema se proporciona información general sobre la funcionalidad de prueba de posicionamiento que proporciona la capa visual. La compatibilidad con la prueba de posicionamiento permite determinar si el valor de una geometría o de un punto pertenece al contenido representado de un objeto Visual, lo que permite implementar comportamientos de interfaz de usuario tal como un rectángulo de selección para seleccionar varios objetos.

Escenarios de pruebas de posicionamiento

La clase UIElement proporciona el método InputHitTest, que le permite hacer pruebas de posicionamiento de un elemento mediante un valor de coordenada asignado. En muchos casos, el método InputHitTest proporciona la funcionalidad deseada para implementar las pruebas de posicionamiento de elementos. Sin embargo, hay varios escenarios en los que puede necesitar implementar pruebas de posicionamiento en la capa visual.

  • Pruebas de posicionamiento de objetos no-UIElement: se aplican si estás haciendo pruebas de posicionamiento para objetos no-UIElement, como DrawingVisual u objetos gráficos.

  • Pruebas de posicionamiento con la utilización de una geometría: esto se aplica si necesita realizar una prueba de posicionamiento con un objeto de geometría en lugar usar el valor de coordenadas de un punto.

  • Pruebas de posicionamiento frente a varios objetos: esto se aplica cuando es necesario realizar pruebas de posicionamiento frente a varios objetos, tales como objetos superpuestos. Puede obtener resultados para todos los elementos visuales que corten una geometría o un punto, no solamente para el primero.

  • Omisión de la directiva de prueba de posicionamiento de UIElement: se aplica cuando es necesario omitir la directiva de prueba de posicionamiento de UIElement, que tiene en cuenta factores tales como si un elemento está deshabilitado o no visible.

Nota

Para obtener un ejemplo de código completo que muestra la prueba de posicionamiento en la capa visual, vea Ejemplo de prueba de posicionamiento con DrawingVisuals y Ejemplo de prueba de posicionamiento de interoperabilidad con Win32.

Compatibilidad con la prueba de posicionamiento

El propósito de los métodos HitTest de la clase VisualTreeHelper es determinar si una geometría o un valor de coordenadas de punto está dentro del límite de un objeto dado, como un control o un elemento gráfico. Por ejemplo, podría utilizar la prueba de posicionamiento para determinar si un clic del mouse dentro del rectángulo delimitador de un objeto pertenece a la geometría de un círculo. También puede optar por invalidar la implementación predeterminada de la prueba de posicionamiento, para realizar cálculos propios en las pruebas de posicionamiento.

La ilustración siguiente 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 posicionamiento válida
Diagrama de región de prueba de posicionamiento válida

Prueba de posicionamiento y orden z

La capa visual de Windows Presentation Foundation (WPF) admite la prueba de posicionamiento frente a todos los objetos situados bajo un punto o una geometría, no solamente para el objeto de nivel superior. Los resultados se devuelven en un orden z. Pero, el objeto visual que se pasa como parámetro al método HitTest determina qué parte del árbol visual se someterá a la prueba de posicionamiento. Puede realizar la prueba de posicionamiento frente al árbol visual completo o frente a cualquier parte de él.

En la ilustración siguiente, el objeto de círculo está encima del objeto cuadrado y del triangular. Si solo le interesa la prueba de posicionamiento del objeto visual cuyo valor de orden z es el superior, puede configurar la enumeración de prueba posicionamiento visual para que devuelva Stop de HitTestResultCallback para detener la exploración transversal de la prueba de posicionamiento después del primer elemento.

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

Si quiere enumerar todos los objetos visuales en un punto o geometría concreta, devuelva Continue de HitTestResultCallback. Esto significa que puede realizar pruebas de posicionamiento para objetos visuales que estén bajo otros objetos, aunque estén completamente ocultos. Vea el ejemplo de código en la sección "Utilizar una devolución de llamada de resultados de pruebas de posicionamiento" para obtener más información.

Nota

Un objeto visual transparente también se puede someter a una prueba de posicionamiento.

Uso de la prueba de posicionamiento predeterminada

Puede identificar si un punto está dentro de la geometría de un objeto visual mediante el método HitTest para especificar el objeto visual y el valor del punto de las coordenadas con las que se comparará la prueba de posicionamiento. El parámetro de objeto visual identifica el punto inicial en el árbol visual para la búsqueda de la prueba de posicionamiento. Si un objeto visual se encuentra en el árbol visual cuya geometría contiene la coordenada, se establece en la propiedad VisualHit de un objeto HitTestResult. Entonces, HitTestResult se devuelve a partir del método HitTest. Si el punto no está contenido en el subárbol visual en el que está haciendo la prueba de posicionamiento, HitTest devuelve null.

Nota

La prueba de posicionamiento predeterminada devuelve el objeto de nivel superior en el orden z. Para identificar todos los objetos visuales, incluso aquellos que puedan estar ocultos, de forma total o parcial, utilice una devolución de llamada de resultado de prueba de posicionamiento.

El valor de coordenadas que pase como parámetro de punto para el método HitTest debe ser relativo al espacio de coordenadas del objeto visual para el que se realiza la prueba de posicionamiento. Por ejemplo, si ha anidado objetos visuales definidos en (100, 100) en el espacio de coordenadas del elemento primario, la prueba de posicionamiento de un elemento secundario visual en (0, 0) es equivalente a la prueba de posicionamiento en (100, 100) en el espacio de coordenadas del elemento primario.

El código siguiente muestra cómo configurar controladores de eventos del mouse para un objeto UIElement que se usa para capturar eventos usados para la prueba de posicionamiento.

// 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 la prueba de posicionamiento

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

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

Nota

La propiedad IsHitTestVisible obtiene o establece un valor que declara si un objeto derivado de UIElement puede devolverse como un resultado de prueba de posicionamiento desde alguna parte del contenido representado. Esto permite modificar de manera selectiva el árbol visual para determinar qué objetos visuales están implicados en una prueba de posicionamiento.

Uso de una devolución de llamada de resultados de prueba de posicionamiento

Puede enumerar todos los objetos visuales de un árbol visual cuya geometría contenga un valor de coordenadas especificado. Esto permite identificar todos los objetos visuales, incluso aquellos que puedan estar ocultos, de forma parcial o total, por otros objetos visuales. Para enumerar los objetos visuales de un árbol visual, use el método HitTest con una función de devolución de llamada de la prueba de posicionamiento. El sistema llama a la función de devolución de llamada de la prueba de posicionamiento cuando el valor de coordenadas especificado esté contenido en un objeto visual.

Durante la enumeración de resultados de pruebas de posicionamiento no se debe realizar ninguna operación que modifique el árbol visual. Agregar o quitar un objeto del árbol visual mientras se recorre puede producir un comportamiento imprevisible. Puede modificar el árbol visual de forma segura tras la devolución del método HitTest. Quizá desee proporcionar una estructura de datos, como un objeto ArrayList, para almacenar los valores durante la enumeración de los resultados de la prueba de posicionamiento.

// 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 devolución de llamada de la prueba de posicionamiento define las acciones que se realizan cuando se identifica una prueba de posicionamiento en un objeto visual determinado del árbol visual. Después de realizar las acciones, se devuelve un valor HitTestResultBehavior que determina si debe continuar la enumeración de los demás objetos visuales.

// 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 de la posición es el orden z. El objeto visual de orden z de nivel superior es el primer objeto enumerado. Los demás objetos visuales enumerados están en orden z decreciente. Este orden de enumeración corresponde al orden de representación de elementos visuales.

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

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

Uso de una devolución de llamada de filtro de prueba de posicionamiento

Puede utilizar un filtro opcional de la prueba de posicionamiento para restringir los objetos que se pasan en los resultados de pruebas de posicionamiento. Esto permite omitir, en los resultados de pruebas de posicionamiento, las partes del árbol visual que no desee procesar. Para implementar un filtro de prueba de posicionamiento, defina una función de devolución de llamada de filtro de prueba de posicionamiento y pásela como un valor de parámetro al llamar 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 de filtro de prueba de posicionamiento, 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 con un filtro de prueba de posicionamiento
Eliminar un árbol visual

La función de devolución de llamada de filtro de prueba de posicionamiento permite enumerar todos los objetos visuales cuyo contenido representado contenga las coordenadas que especifique. No obstante, es posible que desee omitir determinadas ramas del árbol visual, porque no le interese procesarlas en la función de devolución de llamada de los resultados de pruebas de posicionamiento. El valor devuelto de la función de devolución de llamada del filtro de la prueba de posicionamiento determina el tipo de acción que debe realizar la enumeración de los objetos visuales. Por ejemplo, si devuelve el valor, ContinueSkipSelfAndChildren, podrá quitar el objeto visual actual y sus elementos secundarios en la enumeración de los resultados de pruebas de posicionamiento. Esto significa que la función de devolución de llamada de los resultados de pruebas de posicionamiento no verá estos objetos en su enumeración. Cuando se eliminan objetos del árbol visual, se reduce el número de procesos 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 posicionamiento con todos los demás objetos.

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

Se llamará a la devolución de llamada de filtro de la prueba de posicionamiento en aquellos casos en que no se llame a la devolución de llamada de resultados de pruebas de posicionamiento.

Invalidación de la prueba de posicionamiento predeterminada

Puede invalidar la compatibilidad con la prueba de posicionamiento predeterminada de un objeto visual invalidando el método HitTestCore. Esto significa que cuando invoque el método HitTest, se llama a la implementación invalidada de HitTestCore. Se llama al método de invalidación cuando una prueba de posicionamiento está dentro del rectángulo delimitador del objeto visual, aunque las coordenadas estén fuera del contenido representado de dicho objeto.

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

Pueden darse ocasiones en que desee realizar la prueba de posicionamiento tanto respecto al rectángulo delimitador como respecto al contenido representado de un objeto visual. Mediante el uso del valor del parámetro PointHitTestParameters del método invalidado HitTestCore como parámetro para el método base HitTestCore, puede realizar acciones basadas en un posicionamiento en el rectángulo delimitador de un objeto visual y, después, realizar una segunda prueba de posicionamiento respecto al 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

Vea también