處理指標輸入

在 Windows 應用程式中接收、處理和管理來自指向裝置的輸入資料 (例如:觸控、滑鼠、畫筆/手寫筆和觸控板)。

重要

只有在要求清楚、定義良好且平台控制支援的互動不支援您的場景時,才建立自訂互動。
如果您在 Windows 應用程式中自訂互動體驗,使用者需要的是一致、直覺且可探索的體驗。 基於這些原因,建議在平台控制項所支援的互動上建立自訂互動模型。 平台控制項提供完整的 Windows 應用程式使用者互動體驗,包括標準互動、動畫實體效果、視覺回饋和協助工具。

重要 API

指標

大多數互動體驗通常涉及使用者透過使用輸入裝置 (例如:觸控、滑鼠、畫筆/手寫筆和觸控板) 指向他們想要互動的物件來識別該物件。 由於這些輸入裝置所提供的原始人性化介面裝置 (HID) 資料包括許多常用屬性,因此將這些資料升級並整合到統一的輸入堆疊中,並公開為與裝置無關的指標資料。 然後,您的 Windows 應用程式可以使用這些資料,而無需擔心所使用的輸入裝置。

注意

如果您的應用程式需要,也可以從原始 HID 資料升級裝置特有的資訊。

輸入堆疊上的每個輸入點 (或接觸點) 透過由各種指標事件處理常式中的 PointerRoutedEventArgs 參數公開的 Pointer 物件表示。 在多畫筆或多點觸控輸入的情況下,每個接觸點都會被視為唯一的輸入指標。

指標事件

指針事件可公開輸入裝置類型和偵測狀態 (範圍內或相互接觸) 之類的基本資訊,以及位置、壓力和接觸幾何圖形之類的擴充資訊。 此外,還可以使用特定的裝置屬性,例如使用者按下哪個滑鼠按鈕,或是否正在使用畫筆橡皮擦筆尖。 如果您的應用程式需要區分輸入裝置及其功能,請參閱識別輸入裝置

Windows 應用程式可以接聽下列指標事件:

注意

透過在指標事件處理常式中對特定 UI 元素呼叫 CapturePointer 將指標輸入限制在該元素上。 當元素擷取指標時,只有該物件接收指標輸入事件,即使指標移動到該物件的週框區域之外也是如此。 IsInContact (滑鼠按鈕已按下,觸控或觸控筆相互接觸) 必須為 true,CapturePointer 才能成功。

事件 描述

PointerCanceled

當平台取消指標時發生。 這可能會在以下情況中發生:

  • 當在輸入介面範圍內偵測到手寫筆時,會取消觸控指標。
  • 超過 100 毫秒未偵測到使用中的接觸點。
  • 監視器/顯示器已變更 (解析度、設定、多監視器配置)。
  • 桌面已鎖定或使用者已登出。
  • 同時接觸點的數量超出裝置所支援的數量。

PointerCaptureLost

當另一個 UI 元素擷取指標、釋放指標,或以程式設計方式擷取另一個指標時發生。

注意:沒有對應的指標擷取事件。
 

PointerEntered

當指標進入元素的週框區域時發生。 對於觸控、觸控板、滑鼠和手寫筆輸入,這種情況的發生方式可能略有不同。。

  • 觸控需要手指接觸才能觸發此事件,無論是直接觸控元素或是移動到元素的週框區域。
  • 滑鼠和觸控板都有一個永遠會顯示的螢幕游標,即使未按下任何滑鼠或觸控板按鈕也會觸發此事件。
  • 就像觸控一樣,手寫筆透過直接將手寫筆放在元素上或移動到元素的週框區域來觸發此事件。 但是,手寫筆也有懸停狀態 (IsInRange),當為 true 時,會觸發此事件。

PointerExited

當指標離開元素的週框區域時發生。 對於觸控、觸控板、滑鼠和手寫筆輸入,這種情況的發生方式可能略有不同。。

  • 觸控需要手指接觸,當指標移出元素的週框區域時,就會觸發這個事件。
  • 滑鼠和觸控板都有一個永遠會顯示的螢幕游標,即使未按下任何滑鼠或觸控板按鈕也會觸發此事件。
  • 和觸控一樣,手寫筆會在移出該元素的週框區域時觸發此事件。 但是,手寫筆也有懸停狀態 (IsInRange),當狀態從 true 變更為 false 時會觸發此事件。

PointerMoved

當指標變更元素週框區域內座標、按鈕狀態、壓力、傾斜或接觸幾何圖形 (例如:寬度和高度) 時發生。 對於觸控、觸控板、滑鼠和手寫筆輸入,這種情況的發生方式可能略有不同。。

  • 觸控需要手指接觸,並且只在接觸到元素的週框區域內時才會觸發此事件。
  • 滑鼠和觸控板都有一個永遠會顯示的螢幕游標,即使未按下任何滑鼠或觸控板按鈕也會觸發此事件。
  • 就像觸控一樣,手寫筆接觸到元素的週框區域內時觸發此事件。 但是,手寫筆也有懸停狀態 (IsInRange),當狀態為 true 且位於元素的週框區域內時,會觸發此事件。

PointerPressed

在指標指示元素的週框區域內發生按下動作 (例如:按下觸控、按下滑鼠按鈕、按下筆或觸控板按鈕) 時發生。

必須從此事件的處理常式呼叫 CapturePointer

PointerReleased

在指標指示元素的週框區域內發生釋放事件 (例如:放開觸控、放開滑鼠按鈕、抬起筆或放開觸控板按鈕) 時或在週框區域外擷取到該指標時發生。

PointerWheelChanged

發生於旋轉滑鼠滾輪時。

滑鼠輸入與第一次偵測到滑鼠輸入時指派的單一指標相關聯。 按一下滑鼠按鈕 (左鍵、滾輪或向右鍵) 會透過 PointerMoved 事件在指標和該按鈕之間建立一個輔助關聯。

 

指標事件範例

以下是一些來自基本指標追蹤應用程式的程式碼片段,示範如何接聽和處理多個指標的事件,以及取得相關指標的各種屬性。

Pointer application UI

指標輸入範例 (基本) 下載此範例

建立 UI

在本範例中,我們使用 Rectangle (Target) 做為使用指標輸入的物件。 當指標狀態變更時,目標色彩會變更。

每個指標的詳細資訊會顯示在浮動的 TextBlock 中,它在指標移動時跟隨指標移動。 指標事件本身會在矩形右側的 RichTextBlock 中報告。

這是本範例中 UI 的 Extensible Application Markup Language (XAML)。

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

接聽指標事件

在大部分情況下,建議您透過 事件處理常式的 PointerRoutedEventArgs 來取得指標資訊。

如果事件參數未公開所需的指標詳細資訊,則可以取得透過 PointerRoutedEventArgsGetCurrentPointGetIntermediatePoints 方法公開的擴充 PointerPoint 資訊的存取權限。

以下程式碼會設定全域字典物件來追蹤每個使用中的指標,並為目標物件識別各種指標事件接聽程式。

// 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);
}

處理指標事件

接下來,我們會使用 UI 回饋來示範基本指標事件處理常式。

/// <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);
}
  • 此處理常式會管理 PointerEntered 事件。 我們會將事件新增至事件記錄檔、將指標新增至指標集合,以及顯示指標詳細資訊。
/// <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);
}
  • 此處理常式會管理 PointerMoved 事件。 我們會將事件新增至事件記錄檔,並更新指標詳細資訊。

    重要

    滑鼠輸入與第一次偵測到滑鼠輸入時指派的單一指標相關聯。 按一下滑鼠按鈕 (左鍵、滾輪或向右鍵) 會透過 PointerPressed 事件在指標和該按鈕之間建立一個輔助關聯。 只有在放開相同的滑鼠按鈕時,才觸發 PointerReleased 事件 (在完成該事件之前,其他按鈕無法與指標關聯)。 由於這個獨佔關聯,其他滑鼠按鈕點選會透過 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);
}
  • 此處理常式會管理 PointerWheelChanged 事件。 我們會將事件新增至事件記錄檔、將指標新增至指標陣列 (如有必要),並顯示指標詳細資訊。
/// <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);
}
  • 在已終止與數位板接觸的情況下,此處理常式會管理 PointerReleased 事件。 我們會將事件新增至事件記錄檔、從指標集合移除指標,以及更新指標詳細資訊。
/// <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);
    }
}
  • 此處理常式會管理 PointerExited 事件 (在與數位板保持接觸的情況下)。 我們會將事件新增至事件記錄檔、從指標陣列移除指標,以及更新指標詳細資訊。
/// <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);
}
  • 此處理常式會管理 PointerCanceled 事件。 我們會將事件新增至事件記錄檔、從指標陣列移除指標,以及更新指標詳細資訊。
/// <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);
}
  • 此處理常式會管理 PointerCaptureLost 事件。 我們會將事件新增至事件記錄檔、從指標陣列移除指標,以及更新指標詳細資訊。

    注意

    可能會發生 PointerCaptureLost,而不是 PointerReleased。 指標擷取可能會因各種原因而遺失,包括使用者互動、以程序設計方式擷取另一個指標、呼叫 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);
}

取得指標屬性

如前面所述,您必須從透過 PointerRoutedEventArgsGetCurrentPointGetIntermediatePoints 方法取得的 Windows.UI.Input.PointerPoint 物件取得大部分的擴充指標資訊。 以下程式碼段顯示操作步驟。

  • 首先,我們會為每個指標建立新的 TextBlock
/// <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);
}
  • 然後,我們在與該指標關聯的現有 TextBlock 中,提供一種更新指標資訊的方式。
/// <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);
            }
        }
    }
}
  • 最後,我們會查詢各種指標屬性。
/// <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;
}

主要指標

某些輸入裝置 (例如:觸控數位板或觸控板) 支援的不僅僅是滑鼠或手寫筆的典型單一指標 (在大多數情況下,就像 Surface Hub 那樣支援兩個手寫筆輸入)。

使用 PointerPointerProperties 類別的唯讀 IsPrimary 屬性可以識別並區分單一主指標 (主指標一律是輸入序列期間偵測到的第一個指標)。

藉由識別主要指標,您可以使用它來模擬滑鼠或手寫筆輸入、自訂互動,或提供一些其他特定功能或 UI。

注意

如果在輸入序列期間釋放、取消或遺失主要指標,則在初始化新的輸入序列之前,不會建立主要輸入指標 (當所有指標都已釋放、取消或遺失後,輸入序列才會結束)。

主要指標動畫範例

這些程式碼片段示範如何提供專門的視覺回饋,以協助使用者區分應用程式中的指標輸入。

這個特定應用程式同時使用色彩和動畫來強調顯示主要指標。

Pointer application with animated visual feedback

指標輸入範例 (具有動畫的 UserControl) 下載此範例

視覺化回饋

我們根據 XAML Ellipse 物件定義 UserControl,強調顯示每個指標在畫布上的位置,並使用分鏡腳本來顯示主要指標的橢圓形動畫。

以下是 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>

以下是程式碼後置:

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));
    }
}

建立 UI

本範例中的 UI 僅限於輸入 Canvas,我們在其中追蹤所有指標並轉譯指標指示器和主指標動畫 (如果適用),同時顯示包含指標計數器和主指標識別碼的標題列。

以下是 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>

處理指標事件

最後,我們在 MainPage.xaml.cs 程式碼後置檔案中定義基本指標事件處理常式。 我們不會在這裡重現程式碼,因為上一個範例已經介紹基本概念,但您可以從指標輸入範例 ( (具有動畫的 UserControl))下載工作範例。

主題範例

其他範例

封存範例