Partilhar via


Teste de acertos na camada visual

Este tópico fornece uma visão geral da funcionalidade de teste de ocorrências fornecida pela camada visual. O suporte ao teste de colisão permite determinar se uma geometria ou um valor de ponto está incluído no conteúdo renderizado de um Visual, permitindo implementar comportamentos da interface, como um retângulo de seleção para selecionar vários objetos.

Cenários de teste de acertos

A classe UIElement fornece o método InputHitTest, que permite realizar um teste de colisão com um elemento usando um determinado valor de coordenada. Em muitos casos, o método InputHitTest fornece a funcionalidade desejada para implementar o teste de colisão de elementos. No entanto, existem vários cenários nos quais poderá necessitar implementar o teste de colisão no nível visual.

  • Teste de colisão contra objetos nãoUIElement: Isto aplica-se se estiver a testar colisão com objetos nãoUIElement, como por exemplo objetos DrawingVisual ou gráficos.

  • Teste de acerto usando uma geometria: Isso se aplica se você precisar acertar o teste usando um objeto de geometria em vez do valor de coordenadas de um ponto.

  • Teste de colisão em vários objetos: Isto aplica-se quando é necessário realizar testes de colisão em vários objetos, como objetos sobrepostos. Você pode obter resultados para todos os elementos visuais que cruzam uma geometria ou ponto, não apenas o primeiro.

  • Ignorando a política de teste de colisão UIElement: isso aplica-se quando é necessário ignorar a política de teste de colisão UIElement, que leva em consideração fatores como se um elemento está desativado ou invisível.

Observação

Para obter um exemplo de código completo ilustrando o teste de colisão na camada visual, consulte Teste de colisão usando o exemplo DrawingVisuals e Teste de colisão com o exemplo de interoperação Win32.

Suporte ao teste de colisão

O objetivo dos métodos HitTest na classe VisualTreeHelper é determinar se um valor de geometria ou coordenada de ponto está dentro do conteúdo renderizado de um determinado objeto, como um controle ou elemento gráfico. Por exemplo, você pode usar o teste de acerto para determinar se um clique do mouse dentro do retângulo delimitador de um objeto está dentro da geometria de um círculo. Você também pode optar por substituir a implementação padrão do teste de colisão para executar os seus próprios cálculos personalizados de teste de colisão.

A ilustração a seguir mostra a relação entre a região de um objeto não retangular e seu retângulo delimitador.

Diagrama da região de teste de acerto válida
Diagrama da região de teste de colisão válida

Teste de Hit e Z-Order

A camada visual do Windows Presentation Foundation (WPF) oferece suporte ao teste de colisão em todos os objetos sob um ponto ou geometria, não apenas no objeto mais superficial. Os resultados são retornados em ordem z. No entanto, o objeto visual que você passa como parâmetro para o método HitTest determina qual parte da árvore visual que será atingida pelo teste. Você pode bater teste contra toda a árvore visual, ou qualquer parte dela.

Na ilustração a seguir, o objeto círculo está acima dos objetos quadrado e triângulo. Se você estiver interessado apenas em testar o objeto visual cujo valor z-order é o mais alto, você pode definir a enumeração visual hit test para retornar Stop do HitTestResultCallback para parar a travessia do teste de acerto após o primeiro item.

Diagrama da ordem z de uma árvore visual
Diagrama da ordem z de uma árvore visual

Se você quiser enumerar todos os objetos visuais sob um ponto ou geometria específica, retorne Continue do HitTestResultCallback. Isso significa que você pode testar objetos visuais que estão abaixo de outros objetos, mesmo que eles estejam totalmente obscurecidos. Consulte o código de exemplo na seção "Utilizando uma função de retorno para os resultados do teste de interseção" para mais informações.

Observação

Um objeto visual que é transparente também pode ser atingido teste.

Usando o teste de colisão padrão

Você pode identificar se um ponto está dentro da geometria de um objeto visual, usando o método HitTest para especificar um objeto visual e um valor de coordenada de ponto para testar. O parâmetro visual object identifica o ponto de partida na árvore visual para a pesquisa de teste de acertos. Se um objeto visual for encontrado na árvore visual cuja geometria contém a coordenada, ele será definido como a propriedade VisualHit de um objeto HitTestResult. O HitTestResult é então retornado do método HitTest. Se o ponto não estiver contido na subárvore visual que está a testar, HitTest retorna null.

Observação

O teste de colisão padrão sempre retorna o objeto no topo na ordem z. Para identificar todos os elementos visuais, mesmo aqueles que podem estar parcial ou totalmente obscurecidos, use um callback de resultado de teste de colisão.

O valor da coordenada que o utilizador passa como parâmetro point para o método HitTest deve ser relativo ao sistema de coordenadas do objeto visual contra o qual está a realizar o teste de colisão. Por exemplo, se tiver objetos visuais aninhados definidos em (100, 100) no espaço de coordenadas do pai, realizar um teste de acerto num visual filho em (0, 0) equivale a realizar um teste de acerto em (100, 100) no espaço de coordenadas do pai.

O código a seguir mostra como configurar manipuladores de eventos do rato para um objeto UIElement utilizado para capturar eventos destinados ao teste de colisão.

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

Como a árvore visual afeta os testes de clique

O ponto de partida na árvore visual determina quais objetos são retornados durante a enumeração de objetos do teste de acerto. Se tiver vários objetos que deseja efetuar o teste, o objeto visual usado como ponto de partida na árvore visual deve ser o ancestral comum de todos os objetos de interesse. Por exemplo, se estivesses interessado em testar tanto o elemento de botão como a representação visual no diagrama a seguir, terias de definir como ponto de partida na árvore visual o ancestral comum de ambos. Neste caso, o elemento canvas é o ancestral comum do elemento botão e do elemento gráfico.

Diagrama de uma hierarquia de árvore visual
Diagrama de uma hierarquia de árvore visual

Observação

A propriedade IsHitTestVisible obtém ou define um valor que declara se um objeto derivado de UIElementpode ser retornado como um resultado de teste de acertos de alguma parte de seu conteúdo renderizado. Isto permite-lhe alterar seletivamente a árvore visual para determinar quais objetos visuais estão envolvidos num teste de colisão.

Utilizando uma função de retorno para o resultado de um teste de interseção

Você pode enumerar todos os objetos visuais em uma árvore visual cuja geometria contém um valor de coordenada especificado. Isso permite que você identifique todos os objetos visuais, mesmo aqueles que podem ser parcial ou totalmente obscurecidos por outros objetos visuais. Para enumerar objetos visuais em uma árvore visual, use o método HitTest com uma função de retorno de chamada de teste de ocorrência. A função callback de teste de colisão é chamada pelo sistema quando o valor de coordenada especificado está contido num objeto visual.

Durante a enumeração dos resultados do teste de colisão, deve-se evitar executar qualquer operação que modifique a árvore visual. Adicionar ou remover um objeto da árvore visual enquanto ele está sendo percorrido pode resultar em um comportamento imprevisível. Você pode modificar com segurança a árvore visual depois que o método HitTest retorna. Talvez queira fornecer uma estrutura de dados, como um ArrayList, para armazenar valores durante a enumeração dos resultados do teste 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

O método de retorno de chamada do teste de colisão define as ações que conduzes quando um teste de colisão é identificado num objeto visual específico na árvore visual. Depois de executar as ações, você retorna um valor HitTestResultBehavior que determina se a enumeração de quaisquer outros objetos visuais deve continuar ou não.

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

Observação

A ordem de enumeração de objetos visuais atingidos é por ordem z. O objeto visual no nível de ordem z mais alto é o primeiro objeto enumerado. Quaisquer outros objetos visuais enumerados estão no nível decrescente de ordem z. Esta ordem de enumeração corresponde à ordem de renderização dos elementos visuais.

Você pode parar a enumeração de objetos visuais a qualquer momento na função de retorno de chamada de teste de colisão retornando Stop.

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

Usando um retorno de chamada do filtro de teste de visitas

Pode usar um filtro opcional de teste de colisão para restringir os objetos que são transferidos para os resultados do teste de colisão. Isso permite que você ignore partes da árvore visual que você não está interessado em processar em seus resultados de teste de acerto. Para implementar um filtro de teste de acertos, defina uma função de retorno de chamada do filtro de teste de acertos e passe como parâmetro quando chamar o 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

Se você não quiser fornecer a função opcional de retorno de chamada do filtro de teste de ocorrência, passe um valor null como parâmetro para o 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.

Poda de uma árvore visual usando um filtro de teste de interseção
Podar uma árvore visual

A função de 'callback' do filtro de teste de batida permite enumerar todos os elementos visuais cujo conteúdo renderizado contém as coordenadas que especificar. No entanto, você pode querer ignorar certos ramos da árvore visual que você não está interessado em processar em sua função de retorno de chamada de resultados de teste de acerto. O valor de retorno da função de retorno de chamada do filtro de teste de acerto determina o tipo de ação que a enumeração dos objetos visuais deve executar. Por exemplo, se você retornar o valor, ContinueSkipSelfAndChildren, poderá remover o objeto visual atual e seus filhos da enumeração de resultados do teste de acerto. Isso significa que a função callback dos resultados do teste de colisão não terá visibilidade desses objetos na sua enumeração. A poda da árvore visual de objetos diminui a quantidade de processamento durante o processo de enumeração dos resultados do teste de colisão. No exemplo de código a seguir, o filtro ignora etiquetas e os seus descendentes e realiza testes de colisão em todo o resto.

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

Observação

O retorno de chamada do filtro de teste de acertos às vezes pode ser chamado nos casos nos quais o retorno de chamada de resultados do teste de acertos não é chamado.

Substituindo o teste de colisão padrão

Você pode substituir o suporte padrão de "hit testing" de um objeto visual substituindo o método HitTestCore. Isso significa que, quando invocas o método HitTest, a tua implementação substituta de HitTestCore é chamada. O seu método substituído é chamado quando um teste de colisão ocorre dentro do retângulo delimitador do objeto visual, mesmo que a coordenada se situe fora do conteúdo renderizado do 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

Pode haver ocasiões em que se deseje testar tanto o retângulo delimitador quanto o conteúdo renderizado de um objeto visual. Usando o valor do parâmetro PointHitTestParameters no seu método HitTestCore substituído como o parâmetro para o método base HitTestCore, você pode realizar ações com base em acertar o retângulo delimitador de um objeto visual e, em seguida, realizar um segundo teste de acerto no conteúdo renderizado do 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

Ver também