ポインター入力を処理する

Windows アプリケーションのポインティング デバイス (タッチ、マウス、ペン/スタイラス、タッチパッドなど) から入力データを受信、処理、管理します。

Important

明確で明確に定義された要件があり、プラットフォーム コントロールでサポートされている対話がシナリオをサポートしていない場合にのみ、カスタム対話を作成します。
Windows アプリケーションで対話エクスペリエンスをカスタマイズする場合、ユーザーは一貫性があり、直感的で、検出可能であることが期待されます。 これらの理由から、 プラットフォーム コントロールでサポートされているものに対してカスタム操作をモデル化することをお勧めします。 プラットフォーム コントロールは、標準的な操作、アニメーション化された物理効果、視覚的なフィードバック、アクセシビリティなど、Windows appなユーザー操作エクスペリエンスを提供します。

重要な API

ポインター

通常、ほとんどの操作エクスペリエンスには、タッチ、マウス、ペン/スタイラス、タッチパッドなどの入力デバイスを使用して、操作するオブジェクトを識別するユーザーが含まれます。 これらの入力デバイスによって提供される生のヒューマン インターフェイス デバイス (HID) データには多くの共通プロパティが含まれているため、データは昇格され、統合された入力スタックに統合され、デバイスに依存しないポインター データとして公開されます。 その後、Windows アプリケーションは、入力デバイスが使用されていることを気にせずに、このデータを使用できます。

注

デバイス固有の情報は、アプリで必要な場合に生の HID データからも昇格されます。

入力スタック上の各入力ポイント (または連絡先) は、さまざまなポインター イベント ハンドラーの PointerRoutedEventArgs パラメーターを介して公開される Pointer オブジェクトによって表されます。 マルチペン入力またはマルチタッチ入力の場合、各接触は一意の入力ポインターとして扱われます。

ポインター イベント

ポインター イベントは、入力デバイスの種類や検出状態 (範囲内または接触) などの基本情報と、場所、圧力、接触ジオメトリなどの拡張情報を公開します。 また、ユーザーが押したマウス ボタンやペン消しゴムの先端が使用されているかどうかなど、特定のデバイス プロパティも利用できます。 アプリで入力デバイスとその機能を区別する必要がある場合は、「 入力デバイスの識別」を参照してください。

Windows アプリは、次のポインター イベントをリッスンできます。

注

ポインター イベント ハンドラー内のその要素に対して CapturePointer を呼び出して、特定の UI 要素へのポインター入力を制限します。 ポインターが要素によってキャプチャされると、ポインターがオブジェクトの境界領域の外に移動した場合でも、そのオブジェクトのみがポインター入力イベントを受け取ります。 CapturePointer を正常に実行するには、IsInContact (マウス ボタンが押された、タッチまたはスタイラスが接触している) が true である必要があります。

Event Description
PointerCanceled プラットフォームによってポインターが取り消されたときに発生します。 これは、次の状況で発生する可能性があります。
•タッチポインターは、入力面の範囲内でペンが検出されるとキャンセルされます。
• 100 ミリ秒以上、アクティブな接続が検出されません。
• モニター/ディスプレイの変更 (解像度、設定、マルチモン構成)。
• デスクトップがロックされているか、ユーザーがログオフしています。
•同時連絡先の数は、デバイスでサポートされている数を超えました。
PointerCaptureLost 別の UI 要素がポインターをキャプチャしたとき、ポインターが解放されたとき、または別のポインターがプログラムによってキャプチャされたときに発生します。
メモ: 対応するポインター キャプチャ イベントはありません。
ポインタが入力されました ポインターが要素の境界領域に入ったときに発生します。 これは、タッチ、タッチパッド、マウス、ペン入力の場合、少し異なる方法で発生する可能性があります。
•タッチでは、要素を直接タッチするか、要素の境界領域に指が移動することによって、このイベントを発生させるために指の接触が必要です。
• マウスとタッチパッドの両方には、常に表示される画面上のカーソルがあり、たとえマウスやタッチパッドのボタンが押されていない場合でもこのイベントを発生します。
• タッチ同様に、ペンが要素に直接触れるか、要素の境界領域に入ると、このイベントを発生させます。 ただし、ペンにはホバー状態 (IsInRange) もあり、true の場合、このイベントが発生します。
PointerExited ポインターが要素の境界領域から離れると発生します。 これは、タッチ、タッチパッド、マウス、ペン入力の場合、少し異なる方法で発生する可能性があります。
•タッチは指の接触を必要とし、ポインターが要素の境界領域から移動したときにこのイベントを発生させます。
• マウスとタッチパッドの両方には、常に表示される画面上のカーソルがあり、たとえマウスやタッチパッドのボタンが押されていない場合でもこのイベントを発生します。
•タッチと同様に、ペンは要素の境界領域から移動するときにこのイベントを発生します。 ただし、ペンにはホバー状態 (IsInRange) もあり、状態が true から false に変わると、このイベントが発生します。
ポインタが移動しました ポインターが要素の境界領域内の座標、ボタンの状態、圧力、傾き、または接触ジオメトリ (幅や高さなど) を変更したときに発生します。 これは、タッチ、タッチパッド、マウス、ペン入力の場合、少し異なる方法で発生する可能性があります。
•タッチは、指の接触を必要とし、要素の境界領域内に接触している場合にのみ、このイベントを発生させます。
• マウスとタッチパッドの両方には、常に表示される画面上のカーソルがあり、たとえマウスやタッチパッドのボタンが押されていない場合でもこのイベントを発生します。
•タッチと同様に、ペンは要素の境界領域内に接触したときにこのイベントを発生します。 ただし、ペンにはホバー状態 (IsInRange) もあり、その状態が true で要素の境界領域内にある場合にこのイベントがトリガーされます。
PointerPressed ポインターが要素の境界領域内の押下操作 (タッチダウン、マウス ボタンダウン、ペンダウン、タッチパッド ボタンダウンなど) を示す場合に発生します。
CapturePointer は、このイベントのハンドラーから呼び出す必要があります。
ポインターリリース ポインターが要素の境界領域内のリリース アクション (タッチアップ、マウス ボタンの上、ペンの上、タッチパッド のボタンアップなど) を示す場合、またはポインターがキャプチャされた場合に境界領域の外側で発生します。
ポインタホイール変更 マウス ホイールが回転したときに発生します。
マウス入力は、マウス入力が最初に検出されたときに割り当てられた 1 つのポインターに関連付けられます。 マウス ボタン (左、ホイール、または右) をクリックすると、 PointerMoved イベントを通じてポインターとそのボタンの間に二次的な関連付けが作成されます。

ポインター イベントの例

複数のポインターのイベントをリッスンして処理し、関連付けられているポインターのさまざまなプロパティを取得する方法を示す、基本的なポインター追跡アプリのコード スニペットを次に示します。

ポインター アプリケーション UI

"Pointer 入力サンプル(basic)" からこのサンプルをダウンロードします。

注

このサンプルでは、UWP プロジェクト構造を使用します。 示されているポインター イベント API (PointerPressed、 PointerMovedなど) と PointerRoutedEventArgs パターンは、 Microsoft.UI.Xaml.Input 名前空間を使用する WinUI 3 デスクトップ アプリでも同じように動作します。

UI を作成する

この例では、ポインター入力を使用するオブジェクトとして Rectangle (Target) を使用します。 ポインターの状態が変わると、ターゲットの色が変わります。

各ポインターの詳細は、ポインターの移動時に続く浮動 TextBlock に表示されます。 ポインター イベント自体は、四角形の右側にある RichTextBlock で報告されます。

これは、この例の UI の拡張アプリケーション マークアップ言語 (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 を介してポインター情報を取得することをお勧めします。

イベント引数で必要なポインターの詳細が公開されない場合は、GetCurrentPointおよびGetIntermediatePointsのメソッドを使用してPointerRoutedEventArgsから公開される拡張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, Microsoft.UI.Xaml.Input.Pointer> pointers;

public MainPage()
{
    this.InitializeComponent();

    // Initialize the dictionary.
    pointers = new Dictionary<uint, Microsoft.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(Microsoft.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(Microsoft.UI.Colors.Blue);
    }

    // Display pointer details.
    CreateInfoPop(ptrPt);
}
  • このハンドラーは 、PointerMoved イベントを 管理します。 イベント ログにイベントを追加し、ポインターの詳細を更新します。

    Important

    マウス入力は、マウス入力が最初に検出されたときに割り当てられた 1 つのポインターに関連付けられます。 マウス ボタン (左、ホイール、または右) をクリックすると、 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(Microsoft.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(Microsoft.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(Microsoft.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(Microsoft.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(Microsoft.UI.Colors.Black);
    }

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

    DestroyInfoPop(ptrPt);
}

ポインターのプロパティを取得する

前述のように、ほとんどの拡張ポインター情報は、PointerRoutedEventArgs の GetCurrentPoint メソッドおよび GetIntermediatePoints メソッドを使用して取得した Microsoft.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(Microsoft.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() == "Microsoft.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 では 2 つのペン入力がサポートされるため)。

PointerPointerProperties クラスの読み取り専用 IsPrimary プロパティを使用して、1 つのプライマリ ポインターを識別および区別します (プライマリ ポインターは常に、入力シーケンス中に検出された最初のポインターです)。

プライマリ ポインターを識別することで、マウスまたはペン入力のエミュレート、相互作用のカスタマイズ、その他の特定の機能や UI の提供に使用できます。

注

入力シーケンス中にプライマリ ポインターが解放、キャンセル、または失われた場合、プライマリ入力ポインターは、新しい入力シーケンスが開始されるまで作成されません (すべてのポインターが解放、キャンセル、または失われたときに入力シーケンスが終了します)。

プライマリ ポインター アニメーションの例

これらのコード スニペットは、アプリケーション内のポインター入力をユーザーが区別するのに役立つ特別な視覚的フィードバックを提供する方法を示しています。

この特定のアプリでは、色とアニメーションの両方を使用して、プライマリ ポインターを強調表示します。

アニメーション化されたビジュアル フィードバックを含むポインター アプリケーション

このサンプルをダウンロードするには、ポインター入力サンプル(アニメーション付き 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 Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.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 は、ポインターを追跡し、ポインター インジケーターとプライマリ ポインター アニメーション (該当する場合) と、ポインター カウンターとプライマリ ポインター識別子を含むヘッダー バーをレンダリングする入力 キャンバス に制限されています。

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分離コードで基本的なポインター イベント ハンドラーを定義します。 前の例で基本が説明されていたので、ここではコードを再現しませんが、Pointer 入力サンプル (アニメーション付き UserControl) から作業サンプルをダウンロードできます。

トピックのサンプル

その他のサンプル

アーカイブサンプル