Partilhar via


Manipulação de entrada por ponteiro

Recebe, processe e gere dados de entrada de dispositivos apontadores (como táteis, rato, caneta/caneta e touchpad) nas suas aplicações Windows.

Importante

Crie interações personalizadas apenas se houver um requisito claro e bem definido e as interações suportadas pelos controlos da plataforma não suportarem o seu cenário.
Se personalizar as experiências de interação na sua aplicação Windows, os utilizadores esperam que sejam consistentes, intuitivas e acessíveis. Por estas razões, recomendamos que modele as suas interações personalizadas com base naquelas suportadas pelos controlos da plataforma. Os controlos da plataforma proporcionam toda a experiência de interação do utilizador da aplicação Windows, incluindo interações padrão, efeitos físicos animados, feedback visual e acessibilidade.

APIs importantes

Ponteiros

A maioria das experiências de interação envolve tipicamente o utilizador a identificar o objeto com que pretende interagir, apontando-o através de dispositivos de entrada como toque, rato, caneta/stylus e touchpad. Como os dados brutos do Dispositivo de Interface Humana (HID) fornecidos por estes dispositivos de entrada incluem muitas propriedades comuns, os dados são promovidos e consolidados numa pilha de entrada unificada e expostos como dados de ponteiro independentes do dispositivo. As suas aplicações Windows podem então consumir esses dados sem se preocupar com o dispositivo de entrada que está a ser utilizado.

Observação

A informação específica do dispositivo também é promovida a partir dos dados brutos do HID, caso a sua aplicação precise disso.

Cada ponto de entrada (ou contacto) na pilha de entrada é representado por um objeto Pointer exposto através do parâmetro PointerRoutedEventArgs nos vários manipuladores de eventos de ponteiros. No caso de entrada multicaneta ou multitoque, cada contato é tratado como um ponteiro de entrada único.

Eventos de ponteiro

Os eventos de ponteiro revelam informações básicas como o tipo de dispositivo de entrada e o estado de deteção (em alcance ou em contacto), e informações estendidas como localização, pressão e geometria do contacto. Além disso, propriedades específicas do dispositivo, como o botão do rato que o utilizador pressionou ou se a ponta da borracha da caneta está a ser utilizada, também estão disponíveis. Se a sua aplicação precisar de diferenciar entre dispositivos de entrada e as suas capacidades, consulte Identificar dispositivos de entrada.

As aplicações Windows podem detetar os seguintes eventos de ponteiro:

Observação

Restringa a entrada do ponteiro a um elemento específico da interface chamando CapturePointer nesse elemento dentro de um manipulador de eventos do ponteiro. Quando um ponteiro é capturado por um elemento, apenas esse objeto recebe eventos de entrada do apontador, mesmo quando este se move para fora da área delimitadora do objeto. O IsInContact (botão do rato pressionado, toque ou caneta em contacto) deve ser verdadeiro para que o CapturePointer seja bem-sucedido.

Event Description

PointerCanceled

Ocorre quando um ponteiro é cancelado pela plataforma. Isto pode ocorrer nas seguintes circunstâncias:

  • Os ponteiros de toque são cancelados quando uma caneta é detetada dentro do alcance da superfície de entrada.
  • Um contacto ativo não é detetado durante mais de 100 ms.
  • O monitor/ecrã é alterado (resolução, configurações, configuração multi-monitor).
  • O ambiente de trabalho está bloqueado ou o utilizador já fez logout.
  • O número de contactos contemporâneos excede o número suportado pelo dispositivo.

PointerCaptureLost

Ocorre quando outro elemento da interface captura o ponteiro, o ponteiro foi libertado ou outro ponteiro foi capturado programaticamente.

Nota Não existe um evento correspondente de captura de apontadores.
 

PointerEntered

Ocorre quando o ponteiro entra na área delimitada de um elemento. Isto pode ocorrer de formas ligeiramente diferentes para toque, touchpad, mouse e caneta.

  • O toque requer um contacto com o dedo para acionar este evento, seja através de um toque direto no elemento ou movendo-se para a zona delimitada do elemento.
  • O rato e o touchpad têm ambos um cursor no ecrã que está sempre visível e dispara este evento mesmo que não seja pressionado o botão do rato ou touchpad.
  • Tal como o toque, a caneta dispara este evento com uma pressão direta da caneta sobre o elemento ou movendo-se para a área delimitada do elemento. No entanto, a caneta também tem um estado de hover (IsInRange) que, quando certo, dispara este evento.

PointerExited

Ocorre quando um ponteiro sai da área delimitadora de um elemento. Isto pode acontecer de formas ligeiramente diferentes para o toque físico, touchpad, rato e caneta.

  • O toque requer contacto com o dedo e dispara este evento quando o ponteiro sai da área delimitadora do elemento.
  • O rato e o touchpad têm ambos um cursor no ecrã que está sempre visível e dispara este evento mesmo que não seja pressionado o botão do rato ou touchpad.
  • Tal como o toque, a caneta dispara este evento ao sair da área delimitadora do elemento. No entanto, a caneta também tem um estado de hover (IsInRange) que aciona este evento quando o estado muda de verdadeiro para falso.

PointerMoved

Ocorre quando um ponteiro altera coordenadas, estado do botão, pressão, inclinação ou geometria de contacto (por exemplo, largura e altura) dentro da área delimitadora de um elemento. Isto pode acontecer de maneira ligeiramente diferente para toque, touchpad, rato e caneta.

  • O toque requer contacto com o dedo e dispara este evento apenas quando está em contacto dentro da área delimitante do elemento.
  • O rato e o touchpad têm ambos um cursor no ecrã que está sempre visível e dispara este evento mesmo que não seja pressionado o botão do rato ou touchpad.
  • Tal como o toque, a caneta dispara este evento quando está em contacto dentro da área limite do elemento. No entanto, a caneta também tem um estado de hover (IsInRange) que, quando verdadeiro e dentro da área limite do elemento, dispara este evento.

PointerPressed

Ocorre quando o ponteiro indica uma ação de pressão (como toque para baixo, pressionamento do botão do rato, toque da caneta ou pressionamento do botão do touchpad) dentro da área delimitadora de um elemento.

CapturePointer deve ser chamado do handler para este evento.

PointerLibertado

Ocorre quando o ponteiro indica uma ação de libertação (tal como soltar o toque, libertação do botão do rato, caneta levantada ou libertação do botão do touchpad) dentro da área delimitadora de um elemento ou, se o ponteiro for capturado, fora da área delimitadora.

PointerWheelChanged

Ocorre quando a roda do rato é rodada.

A entrada do rato está associada a um único ponteiro atribuído quando a entrada do rato é detetada pela primeira vez. Clicar num botão do rato (esquerda, roda ou direita) cria uma associação secundária entre o ponteiro e esse botão através do evento PointerMoved .

 

Exemplo de evento de ponteiro

Aqui estão alguns excertos de código de uma aplicação básica de rastreamento de ponteiros que mostram como ouvir e gerir eventos de múltiplos ponteiros, e obter várias propriedades para os ponteiros associados.

UI da aplicação de apontadores

Descarregue este exemplo do Amostra de entrada de ponteiro (básico)

Criar a interface do usuário

Neste exemplo, usamos um Retângulo (Target) como objeto que consome a entrada do apontador. A cor do alvo muda quando o estado do ponteiro muda.

Os detalhes de cada ponteiro são exibidos num TextBlock flutuante que segue o ponteiro enquanto este se move. Os próprios eventos do ponteiro são reportados no RichTextBlock à direita do retângulo.

Esta é a Extensible Application Markup Language (XAML) para a interface de utilizador neste exemplo.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="250"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="320" />
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Canvas Name="Container" 
            Grid.Column="0"
            Grid.Row="1"
            HorizontalAlignment="Center" 
            VerticalAlignment="Center" 
            Margin="245,0" 
            Height="320"  Width="640">
        <Rectangle Name="Target" 
                    Fill="#FF0000" 
                    Stroke="Black" 
                    StrokeThickness="0"
                    Height="320" Width="640" />
    </Canvas>
    <Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="3">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Name="buttonClear" 
                Grid.Row="0"
                Content="Clear"
                Foreground="White"
                HorizontalAlignment="Stretch" 
                VerticalAlignment="Stretch">
        </Button>
        <ScrollViewer Name="eventLogScrollViewer" Grid.Row="1" 
                        VerticalScrollMode="Auto" 
                        Background="Black">                
            <RichTextBlock Name="eventLog"  
                        TextWrapping="Wrap" 
                        Foreground="#FFFFFF" 
                        ScrollViewer.VerticalScrollBarVisibility="Visible" 
                        ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                        Grid.ColumnSpan="2">
            </RichTextBlock>
        </ScrollViewer>
    </Grid>
</Grid>

Ouça os eventos de ponteiro

Na maioria das vezes, recomendamos que obtenha informações do ponteiro através dos PointerRoutedEventArgs do manipulador de eventos.

Se o argumento do evento não expor os detalhes do ponteiro necessários, poderá aceder aos detalhes estendidos do PointerPoint expostos através dos métodos GetCurrentPoint e GetIntermediatePoints de PointerRoutedEventArgs.

O código seguinte configura o objeto de dicionário global para rastrear cada ponteiro ativo e identifica os vários ouvintes de eventos de ponteiro para o objeto alvo.

// Dictionary to maintain information about each active pointer. 
// An entry is added during PointerPressed/PointerEntered events and removed 
// during PointerReleased/PointerCaptureLost/PointerCanceled/PointerExited events.
Dictionary<uint, Windows.UI.Xaml.Input.Pointer> pointers;

public MainPage()
{
    this.InitializeComponent();

    // Initialize the dictionary.
    pointers = new Dictionary<uint, Windows.UI.Xaml.Input.Pointer>();

    // Declare the pointer event handlers.
    Target.PointerPressed += 
        new PointerEventHandler(Target_PointerPressed);
    Target.PointerEntered += 
        new PointerEventHandler(Target_PointerEntered);
    Target.PointerReleased += 
        new PointerEventHandler(Target_PointerReleased);
    Target.PointerExited += 
        new PointerEventHandler(Target_PointerExited);
    Target.PointerCanceled += 
        new PointerEventHandler(Target_PointerCanceled);
    Target.PointerCaptureLost += 
        new PointerEventHandler(Target_PointerCaptureLost);
    Target.PointerMoved += 
        new PointerEventHandler(Target_PointerMoved);
    Target.PointerWheelChanged += 
        new PointerEventHandler(Target_PointerWheelChanged);

    buttonClear.Click += 
        new RoutedEventHandler(ButtonClear_Click);
}

Gerir eventos de ponteiro

De seguida, usamos feedback da interface para demonstrar os gestores básicos de eventos de ponteiro.

/// <summary>
/// The pointer pressed event handler.
/// PointerPressed and PointerReleased don't always occur in pairs. 
/// Your app should listen for and handle any event that can conclude 
/// a pointer down (PointerExited, PointerCanceled, PointerCaptureLost).
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
void Target_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Update event log.
    UpdateEventLog("Down: " + ptrPt.PointerId);

    // Lock the pointer to the target.
    Target.CapturePointer(e.Pointer);

    // Update event log.
    UpdateEventLog("Pointer captured: " + ptrPt.PointerId);

    // Check if pointer exists in dictionary (ie, enter occurred prior to press).
    if (!pointers.ContainsKey(ptrPt.PointerId))
    {
        // Add contact to dictionary.
        pointers[ptrPt.PointerId] = e.Pointer;
    }

    // Change background color of target when pointer contact detected.
    Target.Fill = new SolidColorBrush(Windows.UI.Colors.Green);

    // Display pointer details.
    CreateInfoPop(ptrPt);
}
  • Este manipulador gere o evento PointerEntered. Adicionamos o evento ao registo de eventos, adicionamos o ponteiro à coleção de ponteiros e exibimos os detalhes do ponteiro.
/// <summary>
/// The pointer entered event handler.
/// We do not capture the pointer on this event.
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
private void Target_PointerEntered(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Update event log.
    UpdateEventLog("Entered: " + ptrPt.PointerId);

    // Check if pointer already exists (if enter occurred prior to down).
    if (!pointers.ContainsKey(ptrPt.PointerId))
    {
        // Add contact to dictionary.
        pointers[ptrPt.PointerId] = e.Pointer;
    }

    if (pointers.Count == 0)
    {
        // Change background color of target when pointer contact detected.
        Target.Fill = new SolidColorBrush(Windows.UI.Colors.Blue);
    }

    // Display pointer details.
    CreateInfoPop(ptrPt);
}
  • Este manipulador gere o evento PointerMoved . Adicionamos o evento ao registo de eventos e atualizamos os detalhes do ponteiro.

    Importante

    A entrada do rato está associada a um único ponteiro atribuído quando a entrada do rato é detetada pela primeira vez. Clicar num botão do rato (esquerda, roda ou direita) cria uma associação secundária entre o ponteiro e esse botão através do evento PointerPressed . O evento PointerReleased é disparado apenas quando esse mesmo botão do rato é libertado (nenhum outro botão pode ser associado ao ponteiro até que este evento esteja concluído). Devido a esta associação exclusiva, outros cliques do rato são encaminhados através do evento PointerMoved .  

/// <summary>
/// The pointer moved event handler.
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
private void Target_PointerMoved(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Multiple, simultaneous mouse button inputs are processed here.
    // Mouse input is associated with a single pointer assigned when 
    // mouse input is first detected. 
    // Clicking additional mouse buttons (left, wheel, or right) during 
    // the interaction creates secondary associations between those buttons 
    // and the pointer through the pointer pressed event. 
    // The pointer released event is fired only when the last mouse button 
    // associated with the interaction (not necessarily the initial button) 
    // is released. 
    // Because of this exclusive association, other mouse button clicks are 
    // routed through the pointer move event.          
    if (ptrPt.PointerDevice.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse)
    {
        if (ptrPt.Properties.IsLeftButtonPressed)
        {
            UpdateEventLog("Left button: " + ptrPt.PointerId);
        }
        if (ptrPt.Properties.IsMiddleButtonPressed)
        {
            UpdateEventLog("Wheel button: " + ptrPt.PointerId);
        }
        if (ptrPt.Properties.IsRightButtonPressed)
        {
            UpdateEventLog("Right button: " + ptrPt.PointerId);
        }
    }

    // Display pointer details.
    UpdateInfoPop(ptrPt);
}
  • Este manipulador gere o evento PointerWheelChanged . Adicionamos o evento ao registo de eventos, adicionamos o ponteiro ao array de ponteiros (se necessário) e exibimos os detalhes do ponteiro.
/// <summary>
/// The pointer wheel event handler.
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
private void Target_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Update event log.
    UpdateEventLog("Mouse wheel: " + ptrPt.PointerId);

    // Check if pointer already exists (for example, enter occurred prior to wheel).
    if (!pointers.ContainsKey(ptrPt.PointerId))
    {
        // Add contact to dictionary.
        pointers[ptrPt.PointerId] = e.Pointer;
    }

    // Display pointer details.
    CreateInfoPop(ptrPt);
}
  • Este manipulador gere o evento PointerReleased, onde se termina o contato com o digitalizador. Adicionamos o evento ao registo de eventos, removemos o ponteiro da coleção de ponteiros e atualizamos os detalhes do ponteiro.
/// <summary>
/// The pointer released event handler.
/// PointerPressed and PointerReleased don't always occur in pairs. 
/// Your app should listen for and handle any event that can conclude 
/// a pointer down (PointerExited, PointerCanceled, PointerCaptureLost).
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
void Target_PointerReleased(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Update event log.
    UpdateEventLog("Up: " + ptrPt.PointerId);

    // If event source is mouse or touchpad and the pointer is still 
    // over the target, retain pointer and pointer details.
    // Return without removing pointer from pointers dictionary.
    // For this example, we assume a maximum of one mouse pointer.
    if (ptrPt.PointerDevice.PointerDeviceType != Windows.Devices.Input.PointerDeviceType.Mouse)
    {
        // Update target UI.
        Target.Fill = new SolidColorBrush(Windows.UI.Colors.Red);

        DestroyInfoPop(ptrPt);

        // Remove contact from dictionary.
        if (pointers.ContainsKey(ptrPt.PointerId))
        {
            pointers[ptrPt.PointerId] = null;
            pointers.Remove(ptrPt.PointerId);
        }

        // Release the pointer from the target.
        Target.ReleasePointerCapture(e.Pointer);

        // Update event log.
        UpdateEventLog("Pointer released: " + ptrPt.PointerId);
    }
    else
    {
        Target.Fill = new SolidColorBrush(Windows.UI.Colors.Blue);
    }
}
  • Este manipulador gere o evento PointerExited (quando o contacto com o digitalizador é mantido). Adicionamos o evento ao registo de eventos, removemos o ponteiro do array de ponteiros e atualizamos os detalhes do ponteiro.
/// <summary>
/// The pointer exited event handler.
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
private void Target_PointerExited(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Update event log.
    UpdateEventLog("Pointer exited: " + ptrPt.PointerId);

    // Remove contact from dictionary.
    if (pointers.ContainsKey(ptrPt.PointerId))
    {
        pointers[ptrPt.PointerId] = null;
        pointers.Remove(ptrPt.PointerId);
    }

    if (pointers.Count == 0)
    {
        Target.Fill = new SolidColorBrush(Windows.UI.Colors.Red);
    }

    // Update the UI and pointer details.
    DestroyInfoPop(ptrPt);
}
  • Este manipulador gere o evento PointerCanceled. Adicionamos o evento ao registo de eventos, removemos o ponteiro do array de ponteiros e atualizamos os detalhes do ponteiro.
/// <summary>
/// The pointer canceled event handler.
/// Fires for various reasons, including: 
///    - Touch contact canceled by pen coming into range of the surface.
///    - The device doesn't report an active contact for more than 100ms.
///    - The desktop is locked or the user logged off. 
///    - The number of simultaneous contacts exceeded the number supported by the device.
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
private void Target_PointerCanceled(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Update event log.
    UpdateEventLog("Pointer canceled: " + ptrPt.PointerId);

    // Remove contact from dictionary.
    if (pointers.ContainsKey(ptrPt.PointerId))
    {
        pointers[ptrPt.PointerId] = null;
        pointers.Remove(ptrPt.PointerId);
    }

    if (pointers.Count == 0)
    {
        Target.Fill = new SolidColorBrush(Windows.UI.Colors.Black);
    }

    DestroyInfoPop(ptrPt);
}
  • Este manipulador gere o evento PointerCaptureLost . Adicionamos o evento ao registo de eventos, removemos o ponteiro do array de ponteiros e atualizamos os detalhes do ponteiro.

    Observação

    PointerCaptureLost pode ocorrer em vez de PointerReleased. A captura de ponteiros pode ser perdida por várias razões, incluindo interação do utilizador, captura programática de outro ponteiro, chamada de PointerReleased.  

/// <summary>
/// The pointer capture lost event handler.
/// Fires for various reasons, including: 
///    - User interactions
///    - Programmatic capture of another pointer
///    - Captured pointer was deliberately released
// PointerCaptureLost can fire instead of PointerReleased. 
/// </summary>
/// <param name="sender">Source of the pointer event.</param>
/// <param name="e">Event args for the pointer routed event.</param>
private void Target_PointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
    // Prevent most handlers along the event route from handling the same event again.
    e.Handled = true;

    PointerPoint ptrPt = e.GetCurrentPoint(Target);

    // Update event log.
    UpdateEventLog("Pointer capture lost: " + ptrPt.PointerId);

    if (pointers.Count == 0)
    {
        Target.Fill = new SolidColorBrush(Windows.UI.Colors.Black);
    }

    // Remove contact from dictionary.
    if (pointers.ContainsKey(ptrPt.PointerId))
    {
        pointers[ptrPt.PointerId] = null;
        pointers.Remove(ptrPt.PointerId);
    }

    DestroyInfoPop(ptrPt);
}

Obter propriedades do apontador

Como referido anteriormente, deve obter a informação de ponteiro mais estendida de um objeto Windows.UI.Input.PointerPoint obtido através dos métodos GetCurrentPoint e GetIntermediatePoints de PointerRoutedEventArgs. Os seguintes excertos de código mostram como.

/// <summary>
/// Create the pointer info popup.
/// </summary>
/// <param name="ptrPt">Reference to the input pointer.</param>
void CreateInfoPop(PointerPoint ptrPt)
{
    TextBlock pointerDetails = new TextBlock();
    pointerDetails.Name = ptrPt.PointerId.ToString();
    pointerDetails.Foreground = new SolidColorBrush(Windows.UI.Colors.White);
    pointerDetails.Text = QueryPointer(ptrPt);

    TranslateTransform x = new TranslateTransform();
    x.X = ptrPt.Position.X + 20;
    x.Y = ptrPt.Position.Y + 20;
    pointerDetails.RenderTransform = x;

    Container.Children.Add(pointerDetails);
}
  • Depois fornecemos uma forma de atualizar a informação do ponteiro num TextBlock existente associado a esse ponteiro.
/// <summary>
/// Update the pointer info popup.
/// </summary>
/// <param name="ptrPt">Reference to the input pointer.</param>
void UpdateInfoPop(PointerPoint ptrPt)
{
    foreach (var pointerDetails in Container.Children)
    {
        if (pointerDetails.GetType().ToString() == "Windows.UI.Xaml.Controls.TextBlock")
        {
            TextBlock textBlock = (TextBlock)pointerDetails;
            if (textBlock.Name == ptrPt.PointerId.ToString())
            {
                // To get pointer location details, we need extended pointer info.
                // We get the pointer info through the getCurrentPoint method
                // of the event argument. 
                TranslateTransform x = new TranslateTransform();
                x.X = ptrPt.Position.X + 20;
                x.Y = ptrPt.Position.Y + 20;
                pointerDetails.RenderTransform = x;
                textBlock.Text = QueryPointer(ptrPt);
            }
        }
    }
}
  • Finalmente, consultamos várias propriedades de ponteiros.
/// <summary>
/// Get pointer details.
/// </summary>
/// <param name="ptrPt">Reference to the input pointer.</param>
/// <returns>A string composed of pointer details.</returns>
String QueryPointer(PointerPoint ptrPt)
{
    String details = "";

    switch (ptrPt.PointerDevice.PointerDeviceType)
    {
        case Windows.Devices.Input.PointerDeviceType.Mouse:
            details += "\nPointer type: mouse";
            break;
        case Windows.Devices.Input.PointerDeviceType.Pen:
            details += "\nPointer type: pen";
            if (ptrPt.IsInContact)
            {
                details += "\nPressure: " + ptrPt.Properties.Pressure;
                details += "\nrotation: " + ptrPt.Properties.Orientation;
                details += "\nTilt X: " + ptrPt.Properties.XTilt;
                details += "\nTilt Y: " + ptrPt.Properties.YTilt;
                details += "\nBarrel button pressed: " + ptrPt.Properties.IsBarrelButtonPressed;
            }
            break;
        case Windows.Devices.Input.PointerDeviceType.Touch:
            details += "\nPointer type: touch";
            details += "\nrotation: " + ptrPt.Properties.Orientation;
            details += "\nTilt X: " + ptrPt.Properties.XTilt;
            details += "\nTilt Y: " + ptrPt.Properties.YTilt;
            break;
        default:
            details += "\nPointer type: n/a";
            break;
    }

    GeneralTransform gt = Target.TransformToVisual(this);
    Point screenPoint;

    screenPoint = gt.TransformPoint(new Point(ptrPt.Position.X, ptrPt.Position.Y));
    details += "\nPointer Id: " + ptrPt.PointerId.ToString() +
        "\nPointer location (target): " + Math.Round(ptrPt.Position.X) + ", " + Math.Round(ptrPt.Position.Y) +
        "\nPointer location (container): " + Math.Round(screenPoint.X) + ", " + Math.Round(screenPoint.Y);

    return details;
}

Ponteiro primário

Alguns dispositivos de entrada, como um digitalizador tátil ou touchpad, suportam mais do que o típico ponteiro único de um rato ou uma caneta (na maioria dos casos, pois o Surface Hub suporta duas entradas de caneta).

Use a propriedade IsPrimary de apenas leitura da classe PointerPointerProperties para identificar e diferenciar um único ponteiro primário (o ponteiro primário é sempre o primeiro detetado durante uma sequência de entrada).

Ao identificar o ponteiro principal, pode usá-lo para emular entrada de rato ou caneta, personalizar interações ou fornecer alguma outra funcionalidade ou interface específica.

Observação

Se o ponteiro primário for libertado, cancelado ou perdido durante uma sequência de entrada, um ponteiro primário não é criado até que uma nova sequência de entrada seja iniciada (uma sequência de entrada termina quando todos os ponteiros foram libertados, cancelados ou perdidos).

Exemplo de animação do ponteiro primário

Estes excertos de código mostram como pode fornecer feedback visual especial para ajudar o utilizador a diferenciar entre entradas de ponteiro na sua aplicação.

Esta aplicação em particular usa tanto cor como animação para destacar o ponteiro principal.

Aplicação de ponteiro com feedback visual animado

Descarregue esta amostra a partir da amostra de entrada de ponteiro (UserControl com animação)

Feedback visual

Definimos um UserControl, baseado num objeto Ellipse XAML, que destaca onde cada ponteiro está na tela e usa um Storyboard para animar a elipse correspondente ao ponteiro primário.

Aqui está o XAML:

<UserControl
    x:Class="UWP_Pointers.PointerEllipse"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UWP_Pointers"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="100"
    d:DesignWidth="100">

    <UserControl.Resources>
        <Style x:Key="EllipseStyle" TargetType="Ellipse">
            <Setter Property="Transitions">
                <Setter.Value>
                    <TransitionCollection>
                        <ContentThemeTransition/>
                    </TransitionCollection>
                </Setter.Value>
            </Setter>
        </Style>
        
        <Storyboard x:Name="myStoryboard">
            <!-- Animates the value of a Double property between 
            two target values using linear interpolation over the 
            specified Duration. -->
            <DoubleAnimation
              Storyboard.TargetName="ellipse"
              Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)"  
              Duration="0:0:1" 
              AutoReverse="True" 
              RepeatBehavior="Forever" From="1.0" To="1.4">
            </DoubleAnimation>

            <!-- Animates the value of a Double property between 
            two target values using linear interpolation over the 
            specified Duration. -->
            <DoubleAnimation
              Storyboard.TargetName="ellipse"
              Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleX)"  
              Duration="0:0:1" 
              AutoReverse="True" 
              RepeatBehavior="Forever" From="1.0" To="1.4">
            </DoubleAnimation>

            <!-- Animates the value of a Color property between 
            two target values using linear interpolation over the 
            specified Duration. -->
            <ColorAnimation 
                Storyboard.TargetName="ellipse" 
                EnableDependentAnimation="True" 
                Storyboard.TargetProperty="(Fill).(SolidColorBrush.Color)" 
                From="White" To="Red"  Duration="0:0:1" 
                AutoReverse="True" RepeatBehavior="Forever"/>
        </Storyboard>
    </UserControl.Resources>

    <Grid x:Name="CompositionContainer">
        <Ellipse Name="ellipse" 
        StrokeThickness="2" 
        Width="{x:Bind Diameter}" 
        Height="{x:Bind Diameter}"  
        Style="{StaticResource EllipseStyle}" />
    </Grid>
</UserControl>

E aqui está o código por trás:

using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

// The User Control item template is documented at 
// https://go.microsoft.com/fwlink/?LinkId=234236

namespace UWP_Pointers
{
    /// <summary>
    /// Pointer feedback object.
    /// </summary>
    public sealed partial class PointerEllipse : UserControl
    {
        // Reference to the application canvas.
        Canvas canvas;

        /// <summary>
        /// Ellipse UI for pointer feedback.
        /// </summary>
        /// <param name="c">The drawing canvas.</param>
        public PointerEllipse(Canvas c)
        {
            this.InitializeComponent();
            canvas = c;
        }

        /// <summary>
        /// Gets or sets the pointer Id to associate with the PointerEllipse object.
        /// </summary>
        public uint PointerId
        {
            get { return (uint)GetValue(PointerIdProperty); }
            set { SetValue(PointerIdProperty, value); }
        }
        // Using a DependencyProperty as the backing store for PointerId.  
        // This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PointerIdProperty =
            DependencyProperty.Register("PointerId", typeof(uint), 
                typeof(PointerEllipse), new PropertyMetadata(null));


        /// <summary>
        /// Gets or sets whether the associated pointer is Primary.
        /// </summary>
        public bool PrimaryPointer
        {
            get { return (bool)GetValue(PrimaryPointerProperty); }
            set
            {
                SetValue(PrimaryPointerProperty, value);
            }
        }
        // Using a DependencyProperty as the backing store for PrimaryPointer.  
        // This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PrimaryPointerProperty =
            DependencyProperty.Register("PrimaryPointer", typeof(bool), 
                typeof(PointerEllipse), new PropertyMetadata(false));


        /// <summary>
        /// Gets or sets the ellipse style based on whether the pointer is Primary.
        /// </summary>
        public bool PrimaryEllipse 
        {
            get { return (bool)GetValue(PrimaryEllipseProperty); }
            set
            {
                SetValue(PrimaryEllipseProperty, value);
                if (value)
                {
                    SolidColorBrush fillBrush = 
                        (SolidColorBrush)Application.Current.Resources["PrimaryFillBrush"];
                    SolidColorBrush strokeBrush = 
                        (SolidColorBrush)Application.Current.Resources["PrimaryStrokeBrush"];

                    ellipse.Fill = fillBrush;
                    ellipse.Stroke = strokeBrush;
                    ellipse.RenderTransform = new CompositeTransform();
                    ellipse.RenderTransformOrigin = new Point(.5, .5);
                    myStoryboard.Begin();
                }
                else
                {
                    SolidColorBrush fillBrush = 
                        (SolidColorBrush)Application.Current.Resources["SecondaryFillBrush"];
                    SolidColorBrush strokeBrush = 
                        (SolidColorBrush)Application.Current.Resources["SecondaryStrokeBrush"];
                    ellipse.Fill = fillBrush;
                    ellipse.Stroke = strokeBrush;
                }
            }
        }
        // Using a DependencyProperty as the backing store for PrimaryEllipse.  
        // This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PrimaryEllipseProperty =
            DependencyProperty.Register("PrimaryEllipse", 
                typeof(bool), typeof(PointerEllipse), new PropertyMetadata(false));


        /// <summary>
        /// Gets or sets the diameter of the PointerEllipse object.
        /// </summary>
        public int Diameter
        {
            get { return (int)GetValue(DiameterProperty); }
            set { SetValue(DiameterProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Diameter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DiameterProperty =
            DependencyProperty.Register("Diameter", typeof(int), 
                typeof(PointerEllipse), new PropertyMetadata(120));
    }
}

Criar a interface do usuário

A interface neste exemplo limita-se ao Canvas de entrada, onde rastreamos quaisquer ponteiros e renderizamos os indicadores do ponteiro e a animação do ponteiro primário (se aplicável), juntamente com uma barra de cabeçalho contendo um contador de ponteiros e um identificador primário.

Aqui está o MainPage.xaml:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="HeaderPanel" 
                Orientation="Horizontal" 
                Grid.Row="0">
        <StackPanel.Transitions>
            <TransitionCollection>
                <AddDeleteThemeTransition/>
            </TransitionCollection>
        </StackPanel.Transitions>
        <TextBlock x:Name="Header" 
                    Text="Basic pointer tracking sample - IsPrimary" 
                    Style="{ThemeResource HeaderTextBlockStyle}" 
                    Margin="10,0,0,0" />
        <TextBlock x:Name="PointerCounterLabel"
                    VerticalAlignment="Center"                 
                    Style="{ThemeResource BodyTextBlockStyle}"
                    Text="Number of pointers: " 
                    Margin="50,0,0,0"/>
        <TextBlock x:Name="PointerCounter"
                    VerticalAlignment="Center"                 
                    Style="{ThemeResource BodyTextBlockStyle}"
                    Text="0" 
                    Margin="10,0,0,0"/>
        <TextBlock x:Name="PointerPrimaryLabel"
                    VerticalAlignment="Center"                 
                    Style="{ThemeResource BodyTextBlockStyle}"
                    Text="Primary: " 
                    Margin="50,0,0,0"/>
        <TextBlock x:Name="PointerPrimary"
                    VerticalAlignment="Center"                 
                    Style="{ThemeResource BodyTextBlockStyle}"
                    Text="n/a" 
                    Margin="10,0,0,0"/>
    </StackPanel>
    
    <Grid Grid.Row="1">
        <!--The canvas where we render the pointer UI.-->
        <Canvas x:Name="pointerCanvas"/>
    </Grid>
</Grid>

Gerir eventos de ponteiro

Por fim, definimos os nossos manipuladores básicos de eventos de ponteiro no arquivo de código MainPage.xaml.cs associado. Não vamos reproduzir o código aqui, pois o básico foi abordado no exemplo anterior, mas pode descarregar o sample de trabalho a partir do Pointer input sample (UserControl com animação).

Exemplos de tópicos

Outras amostras

Arquivar amostras