處理指標輸入Handle pointer input

在您的通用 Windows 平台 (UWP) 應用程式中,接收、處理及管理來自指標裝置 (例如觸控、滑鼠、畫筆/手寫筆及觸控板) 的輸入資料。Receive, process, and manage input data from pointing devices (such as touch, mouse, pen/stylus, and touchpad) in your Universal Windows Platform (UWP) applications.

重要

只有在需求明確且定義清楚,而且沒有平台控制項支援的互動可以支援您的情況時,才能建立自訂互動。Create custom interactions only if there is a clear, well-defined requirement and the interactions supported by the platform controls don't support your scenario.
若您在您的 Windows 應用程式中自訂互動體驗,使用者會預期他們一致、直覺化且可搜尋到。If you customize the interaction experiences in your Windows application, users expect them to be consistent, intuitive, and discoverable. 基於這些理由,我們建議您在平台控制項支援的項目上為您的自訂互動建模。For these reasons, we recommend that you model your custom interactions on those supported by the platform controls. 這些平台控制項提供完整的通用 Windows 平台 (UWP) 使用者互動體驗,包含標準互動、動畫物理效果、視覺化回饋及協助工具。The platform controls provide the full Universal Windows Platform (UWP) user interaction experience, including standard interactions, animated physics effects, visual feedback, and accessibility.

重要 APIImportant APIs

PointersPointers

大多數互動體驗通常會牽涉到使用者透過使用輸入裝置指向想要互動的物件來識別該物件,例如觸控、滑鼠、畫筆/手寫筆及觸控板。Most interaction experiences typically involve the user identifying the object they want to interact with by pointing at it through input devices such as touch, mouse, pen/stylus, and touchpad. 由於這些輸入裝置所提供的原始人性化介面裝置 (HID) 資料包含許多常用屬性,因此,會將內容升級並合併到整合的輸入堆疊,並公開為與裝置無關的指標資料。Because the raw Human Interface Device (HID) data provided by these input devices includes many common properties, the data is promoted and consolidated into a unified input stack and exposed as device-agnostic pointer data. 您的 UWP 應用程式之後就能取用此資料,而不需擔心所使用的輸入裝置。Your UWP applications can then consume this data without worrying about the input device being used.

注意

裝置特定的資訊也會視您應用程式的需求,從原始 HID 資料升級。Device-specific info is also promoted from the raw HID data should your app require it.

輸入堆疊上的每個輸入點 (或接觸點) 是利用 Pointer 物件來表示,此物件是透過各種不同指標事件處理常式中的 PointerRoutedEventArgs 參數來公開。Each input point (or contact) on the input stack is represented by a Pointer object exposed through the PointerRoutedEventArgs parameter in the various pointer event handlers. 如果有多個手寫筆或是多點觸控輸入,就會將每個接觸點視為不同的單一輸入指標。In the case of multi-pen or multi-touch input, each contact is treated as a unique input pointer.

指標事件Pointer events

指標事件會公開基本資訊,例如輸入裝置類型和偵測狀態 (位於範圍或接觸點內),以及取得延伸資訊 (例如位置、壓力和接觸幾何)。Pointer events expose basic info such as input device type and detection state (in range or in contact), and extended info such as location, pressure, and contact geometry. 此外,也能取得特定的裝置屬性 (例如,使用者按下哪一個滑鼠按鈕,或者是否使用了畫筆橡皮擦的筆尖)。In addition, specific device properties such as which mouse button a user pressed or whether the pen eraser tip is being used are also available. 如果您的應用程式必須區別輸入裝置及其功能,請參閱識別輸入裝置If your app needs to differentiate between input devices and their capabilities, see Identify input devices.

UWP 應用程式可以接聽下列指標事件:UWP apps can listen for the following pointer events:

注意

藉由在指標事件處理常式中的該項目上呼叫 CapturePointer 來輸入特定 UI 項目的限制指標。Constrain pointer input to a specific UI element by calling CapturePointer on that element within a pointer event handler. 當項目擷取指標時,只有該物件會接收到指標輸入事件,即使指標移動到物件的界限區域以外也一樣。When a pointer is captured by an element, only that object receives pointer input events, even when the pointer moves outside the bounding area of the object. IsInContact (按下滑鼠,接觸觸控或手寫筆) 必須為 true,CapturePointer 才會成功。The IsInContact (mouse button pressed, touch or stylus in contact) must be true for CapturePointer to be successful.

事件Event 描述Description

PointerCanceledPointerCanceled

這會在平台取消指標時發生。Occurs when a pointer is canceled by the platform. 此會在以下情況發生:This can occur in the following circumstances:

  • 在輸入表面的範圍內偵測到手寫筆時,即會取消觸控指標。Touch pointers are canceled when a pen is detected within range of the input surface.
  • 偵測作用中接觸點的時間不會超過 100 毫秒。An active contact is not detected for more than 100 ms.
  • 監視器/顯示器已變更 (解析度、設定、多重監視器設定)。Monitor/display is changed (resolution, settings, multi-mon configuration).
  • 桌面已鎖定,或者使用者已登出。The desktop is locked or the user has logged off.
  • 同時發生的接觸點數目超過裝置支援的數目。The number of simultaneous contacts exceeded the number supported by the device.

PointerCaptureLostPointerCaptureLost

在另一個 UI 元素擷取指標、指標被釋放,或另一個指標以程式設計方式被擷取時,即會發生此情況。Occurs when another UI element captures the pointer, the pointer was released, or another pointer was programmatically captured.

請注意  沒有對應的指標捕捉事件。 Note  There is no corresponding pointer capture event.
 

PointerEnteredPointerEntered

當指標進入元素的界限區域時,即會發生此情況。Occurs when a pointer enters the bounding area of an element. 針對觸控、觸控板、滑鼠及手寫筆輸入,發生此情況的方式會稍有不同。This can happen in slightly different ways for touch, touchpad, mouse, and pen input.

  • 觸控需要手指接觸點來引發此事件,不論是從元素上直接向下觸碰,或是移到元素的界限區域內均可。Touch requires a finger contact to fire this event, either from a direct touch down on the element or from moving into the bounding area of the element.
  • 滑鼠和觸控板一律會在螢幕上顯示一個游標,即使沒有按下滑鼠或觸控板按鈕,仍會引發此事件。Mouse and touchpad both have an on-screen cursor that is always visible and fires this event even if no mouse or touchpad button is pressed.
  • 如同觸控,手寫筆會透過從元素上直接向下觸碰,或是移到元素的界限區域內來引發此事件。Like touch, pen fires this event with a direct pen down on the element or from moving into the bounding area of the element. 但是,手寫筆也有暫留狀態 (IsInRange),當此狀態為 true 時,便會引發此事件。However, pen also has a hover state (IsInRange) that, when true, fires this event.

PointerExitedPointerExited

當指標離開元素的界限區域時,即會發生此情況。Occurs when a pointer leaves the bounding area of an element. 針對觸控、觸控板、滑鼠及手寫筆輸入,發生此情況的方式會稍有不同。This can happen in slightly different ways for touch, touchpad, mouse, and pen input.

  • 觸控需要手指接觸點,並且在將指標移出元素的界限區域時引發此事件。Touch requires a finger contact and fires this event when the pointer moves out of the bounding area of the element.
  • 滑鼠和觸控板一律會在螢幕上顯示一個游標,即使沒有按下滑鼠或觸控板按鈕,仍會引發此事件。Mouse and touchpad both have an on-screen cursor that is always visible and fires this event even if no mouse or touchpad button is pressed.
  • 如同觸控,手寫筆會在移出元素的界限區域時引發此事件。Like touch, pen fires this event when moving out of the bounding area of the element. 不過,手寫筆也有暫留狀態 (IsInRange),當此狀態從 true 變更為 false 時,就會引發此事件。However, pen also has a hover state (IsInRange) that fires this event when the state changes from true to false.

PointerMovedPointerMoved

當指標在元素的界限區域內變更座標、按鈕狀態、壓力、傾斜或接觸幾何時 (例如,寬度與高度),就會發生此情況。Occurs when a pointer changes coordinates, button state, pressure, tilt, or contact geometry (for example, width and height) within the bounding area of an element. 針對觸控、觸控板、滑鼠及手寫筆輸入,發生此情況的方式會稍有不同。This can happen in slightly different ways for touch, touchpad, mouse, and pen input.

  • 觸控需要手指接觸點,而且只會在接觸點位於元素的界限區域內時引發此事件。Touch requires a finger contact and fires this event only when in contact within the bounding area of the element.
  • 滑鼠和觸控板一律會在螢幕上顯示一個游標,即使沒有按下滑鼠或觸控板按鈕,仍會引發此事件。Mouse and touchpad both have an on-screen cursor that is always visible and fires this event even if no mouse or touchpad button is pressed.
  • 如同觸控,手寫筆會在接觸點位於元素的界限區域內時引發此事件。Like touch, pen fires this event when in contact within the bounding area of the element. 不過,手寫筆也有暫留狀態 (IsInRange),當此狀態為 True 並位於元素的界線區域內時,就會引發此事件。However, pen also has a hover state (IsInRange) that, when true and within the bounding area of the element, fires this event.

PointerPressedPointerPressed

當指標指出元素界限區域內的按下動作 (例如,觸控向下、滑鼠向下、手寫筆向下或觸控板按鈕向下) 時,即會發生此情況。Occurs when the pointer indicates a press action (such as a touch down, mouse button down, pen down, or touchpad button down) within the bounding area of an element.

CapturePointer 必須從此事件的處理常式中呼叫。CapturePointer must be called from the handler for this event.

PointerReleasedPointerReleased

當指標指出元素界限區域內的放開動作 (例如,觸控向上、滑鼠按鈕向上、手寫筆向上或觸控版按鈕向上),或如果指標在界限區域外部被擷取時,即會發生此情況。Occurs when the pointer indicates a release action (such as a touch up, mouse button up, pen up, or touchpad button up) within the bounding area of an element or, if the pointer is captured, outside the bounding area.

PointerWheelChangedPointerWheelChanged

旋轉滑鼠滾輪時,即會發生此情況。Occurs when the mouse wheel is rotated.

滑鼠輸入會與第一次偵測到滑鼠輸入時指派的單一指標相關聯。Mouse input is associated with a single pointer assigned when mouse input is first detected. 按一下滑鼠按鈕 (左鍵、滾輪或右鍵) 會透過 PointerMoved 事件建立指標與該按鈕的次要關聯。Clicking a mouse button (left, wheel, or right) creates a secondary association between the pointer and that button through the PointerMoved event.

 

指標事件範例Pointer event example

以下提供一些來自基本指標追蹤應用程式的程式碼片段,示範如何進行接聽,以及處理多個指標的事件並取得各種相關聯指標的屬性。Here are some code snippets from a basic pointer tracking app that show how to listen for and handle events for multiple pointers, and get various properties for the associated pointers.

指標應用程式 UI

指標輸入範例下載此範例(基本)Download this sample from Pointer input sample (basic)

建立 UICreate the UI

針對這個範例,我們使用矩形 (Target) 作為取用指標輸入的物件。For this example, we use a Rectangle (Target) as the object consuming pointer input. 當指標狀態變更時,目標的色彩就會變更。The color of the target changes when the pointer status changes.

每個指標的詳細資料都會顯示於浮動的 TextBlock 中,該區塊會與指標一起移動。Details for each pointer are displayed in a floating TextBlock that follows the pointer as it moves. 指標事件本身會在位於矩形右側的 RichTextBlock 中回報。The pointer events themselves are reported in the RichTextBlock to the right of the rectangle.

這是適用於此範例中 UI 的 Extensible Application Markup Language (XAML)。This is the Extensible Application Markup Language (XAML) for the UI in this example.

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

接聽指標事件Listen for pointer events

在大部分情況下,我們建議您透過事件處理常式的 PointerRoutedEventArgs 來取得指標資訊。In most cases, we recommend that you get pointer info through the PointerRoutedEventArgs of the event handler.

如果事件引數未公開所需的指標詳細資料,您可以透過 PointerRoutedEventArgsGetCurrentPointGetIntermediatePoints 方法,取得公開的延伸 PointerPoint 資訊。If the event argument doesn't expose the pointer details required, you can get access to extended PointerPoint info exposed through the GetCurrentPoint and GetIntermediatePoints methods of PointerRoutedEventArgs.

下列程式碼會設定全域字典物件,用於追蹤每一個作用中的指標,並識別目標物件的各種指標事件接聽程式。The following code sets up the global dictionary object for tracking each active pointer, and identifies the various pointer event listeners for the target object.

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

處理指標事件Handle pointer events

接下來,將使用 UI 回饋來示範基本指標事件處理常式。Next, we use UI feedback to demonstrate basic pointer event handlers.

/// <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 事件。This handler manages the PointerEntered event. 我們將事件新增到事件記錄檔、將指標新增到指標集合,並顯示指標詳細資料。We add the event to the event log, add the pointer to the pointer collection, and display the pointer details.
/// <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 事件。This handler manages the PointerMoved event. 我們將事件新增到事件記錄檔,並更新指標詳細資料。We add the event to the event log and update the pointer details.

    重要

    滑鼠輸入會與第一次偵測到滑鼠輸入時指派的單一指標相關聯。Mouse input is associated with a single pointer assigned when mouse input is first detected. 按一下滑鼠按鈕 (左鍵、滾輪或右鍵) 會透過 PointerPressed 事件建立指標與該按鈕的次要關聯。Clicking a mouse button (left, wheel, or right) creates a secondary association between the pointer and that button through the PointerPressed event. 只在放開相同的滑鼠按鈕時才會觸發 PointerReleased 事件 (這個事件完成前,沒有其他按鈕可以與該指標關聯)。The PointerReleased event is fired only when that same mouse button is released (no other button can be associated with the pointer until this event is complete). 由於這個專屬關聯的關係,其他滑鼠按鈕的按一下都會經由 PointerMoved 事件進行路由。Because of this exclusive association, other mouse button clicks are routed through the PointerMoved event.  

/// <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 事件。This handler manages the PointerWheelChanged event. 我們將事件新增到事件記錄檔、將指標新增到指標陣列 (必要時),並顯示指標詳細資料。We add the event to the event log, add the pointer to the pointer array (if necessary), and display the pointer details.
/// <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 事件。This handler manages the PointerReleased event where contact with the digitizer is terminated. 我們將事件新增到事件記錄檔、從指標集合移除指標,並更新指標詳細資料。We add the event to the event log, remove the pointer from the pointer collection, and update the pointer details.
/// <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 事件。This handler manages the PointerExited event (when contact with the digitizer is maintained). 我們將事件新增到事件記錄檔、從指標陣列移除指標,並更新指標詳細資料。We add the event to the event log, remove the pointer from the pointer array, and update the pointer details.
/// <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 事件。This handler manages the PointerCanceled event. 我們將事件新增到事件記錄檔、從指標陣列移除指標,並更新指標詳細資料。We add the event to the event log, remove the pointer from the pointer array, and update the pointer details.
/// <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 事件。This handler manages the PointerCaptureLost event. 我們將事件新增到事件記錄檔、從指標陣列移除指標,並更新指標詳細資料。We add the event to the event log, remove the pointer from the pointer array, and update the pointer details.

    注意

    可能會發生PointerCaptureLost ,而不是PointerReleasedPointerCaptureLost can occur instead of PointerReleased. 指標擷取可能會因為各種原因遺失,包括使用者互動、使用程式設計方式擷取另一個指標、呼叫 PointerReleased 等。Pointer capture can be lost for various reasons including user interaction, programmatic capture of another pointer, calling 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);
}

取得指標屬性Get pointer properties

如稍早所述,您必須透過 PointerRoutedEventArgsGetCurrentPointGetIntermediatePoints 方法,從 Windows.UI.Input.PointerPoint 物件取得最延伸的指標資訊。As stated earlier, you must get most extended pointer info from a Windows.UI.Input.PointerPoint object obtained through the GetCurrentPoint and GetIntermediatePoints methods of PointerRoutedEventArgs. 下列程式碼片段顯示取得的方式。The following code snippets show how.

  • 首先,為每個指標建立一個新的 TextBlockFirst, we create a new TextBlock for each pointer.
/// <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 中的指標資訊。Then we provide a way to update the pointer info in an existing TextBlock associated with that pointer.
/// <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);
            }
        }
    }
}
  • 最後,我們查詢幾個指標屬性。Finally, we query various pointer properties.
/// <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;
}

主要指標Primary pointer

某些輸入裝置,例如觸控數位板或觸控板,可支援比一般單一滑鼠或手寫筆指標更多的指標 (在大多數的案例中為 Surface Hub,因為其支援兩個手寫筆輸入)。Some input devices, such as a touch digitizer or touchpad, support more than the typical single pointer of a mouse or a pen (in most cases as the Surface Hub supports two pen inputs).

請使用 PointerPointerProperties 類別的唯讀 IsPrimary 屬性識別和區別單一主要指標 (主要指標在輸入序列期間永遠都是第一個偵測到的指標)。Use the read-only IsPrimary property of the PointerPointerProperties class to identify and differentiate a single primary pointer (the primary pointer is always the first pointer detected during an input sequence).

藉由識別主要指標,您可以使用它來模擬滑鼠或手寫筆輸入、自訂互動,或提供其他特定功能或 UI。By identifying the primary pointer, you can use it to emulate mouse or pen input, customize interactions, or provide some other specific functionality or UI.

注意

當主要指標在輸入序列期間釋放、取消或遺失時,在新的輸入序列啟動前不會建立主要輸入指標 (輸入序列會在所有指標釋放、取消或遺失時結束)。If the primary pointer is released, canceled, or lost during an input sequence, a primary input pointer is not created until a new input sequence is initiated (an input sequence ends when all pointers have been released, canceled, or lost).

主要指標動畫範例Primary pointer animation example

這些程式碼片段顯示提供特殊視覺回饋,以協助使用者在您應用程式中區別指標輸入的方式。These code snippets show how you can provide special visual feedback to help a user differentiate between pointer inputs in your application.

此特定應用程式使用色彩和動畫醒目提示主要指標。This particular app uses both color and animation to highlight the primary pointer.

帶有動畫視覺回饋的指標應用程式

指標輸入範例下載此範例(使用動畫的 UserControl)Download this sample from Pointer input sample (UserControl with animation)

視覺回饋Visual feedback

我們會根據 XAML Ellipse 物件,定義一個 UserControl 來醒目提示哪個指標正位於畫布上,並使用 Storyboard 來為對應到主要指標的橢圓形建立動畫。We define a UserControl, based on a XAML Ellipse object, that highlights where each pointer is on the canvas and uses a Storyboard to animate the ellipse that corresponds to the primary pointer.

以下是 XAML:Here's the 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>

以下是其後置程式碼:And here's the code-behind:

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

建立 UICreate the UI

此範例中的 UI 會限制在輸入 Canvas 中,我們會在其中追蹤任何指標並轉譯指標指示器和主要指標動畫 (若適用的話),搭配包含指標計數器和主要指標識別識別碼的標頭列。The UI in this example is limited to the input Canvas where we track any pointers and render the pointer indicators and primary pointer animation (if applicable), along with a header bar containing a pointer counter and a primary pointer identifier.

以下是 MainPage.xaml:Here's the 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>

處理指標事件Handle pointer events

最後,我們會在 MainPage.xaml.cs 後置程式碼中定義我們的基本指標事件處理常式。Finally, we define our basic pointer event handlers in the MainPage.xaml.cs code-behind. 我們不會在此處重新產生程式碼,因為我們已經在先前的範例中涵蓋基礎內容,但您仍然可以從指標輸入範例 (附帶動畫的 UserControl) 下載可運作的範例。We won't reproduce the code here as the basics were covered in the previous example, but you can download the working sample from Pointer input sample (UserControl with animation).

主題範例Topic samples

其他範例Other samples

封存範例Archive samples