Windows アプリでのペン操作と Windows Ink

Surface ペンのヒーロー イメージ。
Surface ペン (Microsoft Store購入可能)。

概要

ペン入力向けに Windows アプリを最適化して、標準のポインター デバイス機能と最適な Windows Ink エクスペリエンスをユーザーに提供します。

Note

このトピックでは、Windows Ink プラットフォームについて説明します。 ポインターの一般的な入力処理 (マウス、タッチ、タッチパッドと同様) については、「 ハンドル ポインター入力を参照してください。

Windows アプリでのインクの使用

Windows ペンとインクを使用して、より魅力的なエンタープライズ アプリを構築する

Windows Ink プラットフォームは、ペン デバイスと共に、デジタル手書きのメモ、描画、注釈を作成する自然な方法を提供します。 このプラットフォームでは、デジタイザー入力のインク データとしてのキャプチャ、インク データの生成、インク データの管理、出力デバイスでのインク ストロークとしてのインク データのレンダリング、手書き認識によるインクからテキストへの変換がサポートされています。

ユーザーが書き込みまたは描画するときにペンの基本的な位置と動きをキャプチャするだけでなく、アプリはストローク全体で使用されるさまざまな量の圧力を追跡して収集することもできます。 この情報は、ペン先の図形、サイズ、回転、インクの色、目的 (プレーン インク、消去、強調表示、選択) の設定と共に、ペン、鉛筆、またはブラシを使用した紙の書き込みや描画によく似たユーザー エクスペリエンスを提供できます。

Note

アプリでは、タッチ デジタイザーやマウス デバイスなど、他のポインター ベースのデバイスからのインク入力をサポートすることもできます。 

インク プラットフォームは非常に柔軟です。 要件に応じて、さまざまなレベルの機能をサポートするように設計されています。

Windows Ink UX のガイドラインについては、「 コントロールの作成」を参照してください。

Windows Inkプラットフォームのコンポーネント

コンポーネント 説明
InkCanvas 既定では、ペンからのすべての入力をインクストロークまたは消去ストロークとして受け取って表示する XAML UI プラットフォーム コントロール
InkCanvas の使用方法の詳細については、「 Windows Ink ストロークをテキストとして認識する および Store を使用して Windows Ink ストローク データを取得するを参照してください。
InkPresenter InkCanvas コントロールと共にインスタンス化された分離コードオブジェクト (InkCanvas.InkPresenter プロパティによって公開)。 このオブジェクトは、InkCanvas によって公開されるすべての既定の手描き入力機能と、追加のカスタマイズやパーソナライズのための包括的な API セットを提供します。
InkPresenter の使用方法の詳細については、「 Windows Ink ストロークをテキストとして認識するStore を使用して Windows Ink ストローク データを取得するを参照してください。
InkToolbar 関連するInkCanvasのインク関連機能をアクティブにする、カスタマイズ可能で拡張可能なボタンコレクションを含むXAML UIプラットフォームコントロール
InkToolbar の使用方法の詳細については、Windows 手描き入力アプリへの InkToolbar の追加に関する記事を参照してください。
IInkD2DRenderer 既定 の InkCanvas コントロールではなく、ユニバーサル Windows アプリの指定された Direct2D デバイス コンテキストにインク ストロークをレンダリングできるようにします。 これにより、手描き入力エクスペリエンスを完全にカスタマイズできます。
詳細については、 Complex インクサンプルを参照してください。

InkCanvas を使用した基本的な手描き入力

基本的な手描き入力機能を追加するには、アプリの適切なページに InkCanvas UWP プラットフォーム コントロールを配置するだけです。

既定では、 InkCanvas はペンからのインク入力のみをサポートします。 入力は、色と太さ (太さが 2 ピクセルの黒いボールペン) の既定の設定を使用してインク ストロークとしてレンダリングされるか、ストローク消しゴムとして扱われます (入力が消しゴムの先端または消去ボタンで変更されたペンヒントの場合)。

Note

消しゴムの先端またはボタンが存在しない場合は、ペン先からの入力を消去ストロークとして処理するように InkCanvas を構成できます。

この例では、 InkCanvas によって背景画像がオーバーレイされます。

Note

InkCanvas の Height プロパティと Width プロパティは、子要素 (StackPanel コントロールや Grid コントロールなど) のサイズを自動的に設定する要素の子である場合を除き、既定では 0 です。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
        <TextBlock x:Name="Header"
                   Text="Basic ink sample"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   Margin="10,0,0,0" />            
    </StackPanel>
    <Grid Grid.Row="1">
        <Image Source="Assets\StoreLogo.png" />
        <InkCanvas x:Name="inkCanvas" />
    </Grid>
</Grid>

この一連の画像は、この InkCanvas コントロールによってペン入力がどのようにレンダリングされるかを示しています。

背景イメージを含む空白の InkCanvas のスクリーンショット。 インク ストロークを含む InkCanvas のスクリーンショット。 1 ストロークが消去された InkCanvas のスクリーンショット。
背景イメージを含む空白の InkCanvas インク ストロークを含む InkCanvas 1 つのストロークが消去された InkCanvas (一部ではなく、ストローク全体で消去がどのように動作するかに注意してください)。

InkCanvas コントロールでサポートされる手描き入力機能は、InkPresenter と呼ばれる分離コード オブジェクトによって提供されます。

基本的な手描き入力では、 InkPresenterについて心配する必要はありません。 ただし、 InkCanvas の手描き入力動作をカスタマイズして構成するには、対応する InkPresenter オブジェクトにアクセスする必要があります。

InkPresenter を使用した基本的なカスタマイズ

InkPresenter オブジェクトは、InkCanvas コントロールごとにインスタンス化されます。

Note

InkPresenterを直接インスタンス化することはできません。 代わりに、InkCanvasInkPresenter プロパティを介してアクセスします。 

InkPresenter は、対応する InkCanvas コントロールのすべての既定の手描き入力動作を提供すると共に、ストロークのカスタマイズを追加し、ペン入力をきめ細かく管理するための包括的な API セットを提供します (標準および変更)。 これには、ストローク プロパティ、サポートされている入力デバイスの種類、および入力がオブジェクトによって処理されるか、処理のためにアプリに渡されるかが含まれます。

Note

標準のインク入力 (ペン先または消しゴムの先端/ボタンのいずれか) は、ペン バレル ボタン、マウスの右ボタン、または同様のメカニズムなど、2 つ目のハードウェア アフォーダンスでは変更されません。

既定では、インクはペン入力でのみサポートされています。 ここでは、ペンとマウスの両方の入力データをインク ストロークとして解釈するように InkPresenter を構成します。 また、 InkCanvas へのストロークのレンダリングに使用される初期インク ストローク属性も設定します。

マウスとタッチの手描き入力を有効にするには、InkPresenterInputDeviceTypes プロパティを、必要な CoreInputDeviceTypes 値の組み合わせに設定します。

public MainPage()
{
    this.InitializeComponent();

    // Set supported inking device types.
    inkCanvas.InkPresenter.InputDeviceTypes =
        Windows.UI.Core.CoreInputDeviceTypes.Mouse |
        Windows.UI.Core.CoreInputDeviceTypes.Pen;

    // Set initial ink stroke attributes.
    InkDrawingAttributes drawingAttributes = new InkDrawingAttributes();
    drawingAttributes.Color = Windows.UI.Colors.Black;
    drawingAttributes.IgnorePressure = false;
    drawingAttributes.FitToCurve = true;
    inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
}

インク ストローク属性は、ユーザー設定またはアプリの要件に合わせて動的に設定できます。

ここでは、ユーザーがインクの色の一覧から選択できるようにします。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
        <TextBlock x:Name="Header"
                   Text="Basic ink customization sample"
                   VerticalAlignment="Center"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   Margin="10,0,0,0" />
        <TextBlock Text="Color:"
                   Style="{StaticResource SubheaderTextBlockStyle}"
                   VerticalAlignment="Center"
                   Margin="50,0,10,0"/>
        <ComboBox x:Name="PenColor"
                  VerticalAlignment="Center"
                  SelectedIndex="0"
                  SelectionChanged="OnPenColorChanged">
            <ComboBoxItem Content="Black"/>
            <ComboBoxItem Content="Red"/>
        </ComboBox>
    </StackPanel>
    <Grid Grid.Row="1">
        <Image Source="Assets\StoreLogo.png" />
        <InkCanvas x:Name="inkCanvas" />
    </Grid>
</Grid>

その後、選択した色の変更を処理し、それに応じてインク ストローク属性を更新します。

// Update ink stroke color for new strokes.
private void OnPenColorChanged(object sender, SelectionChangedEventArgs e)
{
    if (inkCanvas != null)
    {
        InkDrawingAttributes drawingAttributes =
            inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();

        string value = ((ComboBoxItem)PenColor.SelectedItem).Content.ToString();

        switch (value)
        {
            case "Black":
                drawingAttributes.Color = Windows.UI.Colors.Black;
                break;
            case "Red":
                drawingAttributes.Color = Windows.UI.Colors.Red;
                break;
            default:
                drawingAttributes.Color = Windows.UI.Colors.Black;
                break;
        };

        inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
    }
}

これらの画像は、 InkPresenter によってペン入力がどのように処理およびカスタマイズされるかを示しています。

既定の黒いインク ストロークを持つ InkCanvas を示すスクリーンショット。

既定の黒いインク ストロークを持つ InkCanvas

ユーザーが赤いインク ストロークを選択した InkCanvas のスクリーンショット。

ユーザーが赤いインク ストロークを選択した InkCanvas

ストロークの選択など、手描き入力や消去以外の機能を提供するには、アプリで未処理の処理のために InkPresenter に対する特定の入力を識別する必要があります。

高度な処理のためのパススルー入力

既定では、 InkPresenter は、ペン バレル ボタン、右マウス ボタンなどのセカンダリ ハードウェア アフォーダンスによって変更された入力を含め、すべての入力をインク ストロークまたは消去ストロークとして処理します。 ただし、ユーザーは通常、これらのセカンダリ アフォーダンスを使用して、いくつかの追加機能または変更された動作を期待します。

場合によっては、セカンダリ アフォーダンスのないペンの追加機能 (通常はペン ヒントに関連付けられていない機能)、その他の入力デバイスの種類、またはアプリの UI でのユーザー選択に基づいて何らかの種類の変更された動作を公開する必要がある場合もあります。

これをサポートするために、 InkPresenter は、特定の入力を未処理のままにするように構成できます。 この未処理の入力は、処理のためにアプリに渡されます。

例 - 未処理の入力を使用してストロークの選択を実装する

Windows Ink プラットフォームでは、ストロークの選択など、変更された入力を必要とするアクションの組み込みサポートは提供されません。 このような機能をサポートするには、アプリにカスタム ソリューションを提供する必要があります。

次のコード例 (すべてのコードは MainPage.xaml および MainPage.xaml.cs ファイルにあります) では、ペン バレル ボタン (またはマウスの右ボタン) を使用して入力が変更されたときにストロークの選択を有効にする方法について説明します。

  1. まず、MainPage.xaml で UI を設定します。

    ここでは、キャンバス ( InkCanvas の下) を追加して、選択ストロークを描画します。 別のレイヤーを使用して選択ストロークを描画すると、 InkCanvas とそのコンテンツはそのまま残ります。

    基になる選択キャンバスを含む空白の InkCanvas のスクリーンショット。

      <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
          <TextBlock x:Name="Header"
            Text="Advanced ink customization sample"
            VerticalAlignment="Center"
            Style="{ThemeResource HeaderTextBlockStyle}"
            Margin="10,0,0,0" />
        </StackPanel>
        <Grid Grid.Row="1">
          <!-- Canvas for displaying selection UI. -->
          <Canvas x:Name="selectionCanvas"/>
          <!-- Inking area -->
          <InkCanvas x:Name="inkCanvas"/>
        </Grid>
      </Grid>
    
  2. MainPage.xaml.csでは、選択 UI の側面への参照を保持するためのグローバル変数をいくつか宣言します。 具体的には、選択したなげなわのストロークと、選択したストロークを強調表示する外接する四角形です。

      // Stroke selection tool.
      private Polyline lasso;
      // Stroke selection area.
      private Rect boundingRect;
    
  3. 次に、 InkPresenter を構成して、ペンとマウスの両方の入力データをインク ストロークとして解釈し、ストロークのレンダリングに使用される初期インク ストローク属性を InkCanvas に設定します。

    最も重要なのは、InkPresenterInputProcessingConfiguration プロパティを使用して、変更された入力をアプリで処理する必要があることを示します。 変更された入力は、InputProcessingConfiguration.RightDragActioninkInputRightDragAction.LeaveUnprocessed の値割り当てることによって指定されます。 この値が設定されると、 InkPresenter は、処理するポインター イベントのセットである InkUnprocessedInput クラスに渡されます。

    未処理の PointerPressedPointerMoved、および PointerReleased InkPresenter によって渡されるイベントのリスナーを割り当てます。 すべての選択機能は、これらのイベントのハンドラーに実装されます。

    最後に、 StrokeStarted および StrokesErased InkPresenter のイベントのリスナーを割り当てます。 これらのイベントのハンドラーを使用して、新しいストロークが開始された場合、または既存のストロークが消去された場合に、選択 UI をクリーンアップします。

    既定の黒いインク ストロークを持つ inkcanvas を示す Advances インクカスタマイズ サンプル アプリのスクリーンショット。

      public MainPage()
      {
        this.InitializeComponent();
    
        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
          Windows.UI.Core.CoreInputDeviceTypes.Mouse |
          Windows.UI.Core.CoreInputDeviceTypes.Pen;
    
        // Set initial ink stroke attributes.
        InkDrawingAttributes drawingAttributes = new InkDrawingAttributes();
        drawingAttributes.Color = Windows.UI.Colors.Black;
        drawingAttributes.IgnorePressure = false;
        drawingAttributes.FitToCurve = true;
        inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
    
        // By default, the InkPresenter processes input modified by
        // a secondary affordance (pen barrel button, right mouse
        // button, or similar) as ink.
        // To pass through modified input to the app for custom processing
        // on the app UI thread instead of the background ink thread, set
        // InputProcessingConfiguration.RightDragAction to LeaveUnprocessed.
        inkCanvas.InkPresenter.InputProcessingConfiguration.RightDragAction =
            InkInputRightDragAction.LeaveUnprocessed;
    
        // Listen for unprocessed pointer events from modified input.
        // The input is used to provide selection functionality.
        inkCanvas.InkPresenter.UnprocessedInput.PointerPressed +=
            UnprocessedInput_PointerPressed;
        inkCanvas.InkPresenter.UnprocessedInput.PointerMoved +=
            UnprocessedInput_PointerMoved;
        inkCanvas.InkPresenter.UnprocessedInput.PointerReleased +=
            UnprocessedInput_PointerReleased;
    
        // Listen for new ink or erase strokes to clean up selection UI.
        inkCanvas.InkPresenter.StrokeInput.StrokeStarted +=
            StrokeInput_StrokeStarted;
        inkCanvas.InkPresenter.StrokesErased +=
            InkPresenter_StrokesErased;
      }
    
  4. 次に、未処理の PointerPressedPointerMoved、および InkPresenter によって渡される PointerReleased イベントのハンドラーを定義します。

    これらのハンドラーでは、なげなわストロークや外接する四角形など、すべての選択機能が実装されています。

    選択されたなげなわのスクリーンショット。

      // Handle unprocessed pointer events from modified input.
      // The input is used to provide selection functionality.
      // Selection UI is drawn on a canvas under the InkCanvas.
      private void UnprocessedInput_PointerPressed(
        InkUnprocessedInput sender, PointerEventArgs args)
      {
        // Initialize a selection lasso.
        lasso = new Polyline()
        {
            Stroke = new SolidColorBrush(Windows.UI.Colors.Blue),
            StrokeThickness = 1,
            StrokeDashArray = new DoubleCollection() { 5, 2 },
            };
    
            lasso.Points.Add(args.CurrentPoint.RawPosition);
    
            selectionCanvas.Children.Add(lasso);
        }
    
        private void UnprocessedInput_PointerMoved(
          InkUnprocessedInput sender, PointerEventArgs args)
        {
          // Add a point to the lasso Polyline object.
          lasso.Points.Add(args.CurrentPoint.RawPosition);
        }
    
        private void UnprocessedInput_PointerReleased(
          InkUnprocessedInput sender, PointerEventArgs args)
        {
          // Add the final point to the Polyline object and
          // select strokes within the lasso area.
          // Draw a bounding box on the selection canvas
          // around the selected ink strokes.
          lasso.Points.Add(args.CurrentPoint.RawPosition);
    
          boundingRect =
            inkCanvas.InkPresenter.StrokeContainer.SelectWithPolyLine(
              lasso.Points);
    
          DrawBoundingRect();
        }
    
  5. PointerReleased イベント ハンドラーを終了するには、すべてのコンテンツ (なげなわストローク) の選択レイヤーをクリアし、なげなわ領域に囲まれたインク ストロークの周囲に単一の外接する四角形を描画します。

    選択範囲の境界の修正のスクリーンショット。

      // Draw a bounding rectangle, on the selection canvas, encompassing
      // all ink strokes within the lasso area.
      private void DrawBoundingRect()
      {
        // Clear all existing content from the selection canvas.
        selectionCanvas.Children.Clear();
    
        // Draw a bounding rectangle only if there are ink strokes
        // within the lasso area.
        if (!((boundingRect.Width == 0) ||
          (boundingRect.Height == 0) ||
          boundingRect.IsEmpty))
          {
            var rectangle = new Rectangle()
            {
              Stroke = new SolidColorBrush(Windows.UI.Colors.Blue),
                StrokeThickness = 1,
                StrokeDashArray = new DoubleCollection() { 5, 2 },
                Width = boundingRect.Width,
                Height = boundingRect.Height
            };
    
            Canvas.SetLeft(rectangle, boundingRect.X);
            Canvas.SetTop(rectangle, boundingRect.Y);
    
            selectionCanvas.Children.Add(rectangle);
          }
        }
    
  6. 最後に、 StrokeStarted および StrokesErased InkPresenter イベントのハンドラーを定義します。

    これらはどちらも同じクリーンアップ関数を呼び出して、新しいストロークが検出されるたびに現在の選択をクリアするだけです。

      // Handle new ink or erase strokes to clean up selection UI.
      private void StrokeInput_StrokeStarted(
        InkStrokeInput sender, Windows.UI.Core.PointerEventArgs args)
      {
        ClearSelection();
      }
    
      private void InkPresenter_StrokesErased(
        InkPresenter sender, InkStrokesErasedEventArgs args)
      {
        ClearSelection();
      }
    
  7. 新しいストロークが開始されたとき、または既存のストロークが消去されたときに、選択キャンバスからすべての選択 UI を削除する関数を次に示します。

      // Clean up selection UI.
      private void ClearSelection()
      {
        var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
        foreach (var stroke in strokes)
        {
          stroke.Selected = false;
        }
        ClearDrawnBoundingRect();
       }
    
      private void ClearDrawnBoundingRect()
      {
        if (selectionCanvas.Children.Any())
        {
          selectionCanvas.Children.Clear();
          boundingRect = Rect.Empty;
        }
      }
    

カスタム レンダリング インク

既定では、インク入力は待機時間の短いバックグラウンド スレッドで処理され、描画時に進行中 ("wet" ) でレンダリングされます。 ストロークが完了すると (ペンまたは指が持ち上げられたり、マウスボタンが離される)、ストロークは UI スレッドで処理され、InkCanvas レイヤーに "ドライ" 状態でレンダリングされます (アプリケーションコンテンツの上にあり、ウェット インクを置き換えます)。

この既定の動作をオーバーライドし、ウェット インク ストロークを "カスタム乾燥" することで、手描き入力エクスペリエンスを完全に制御できます。 通常、既定の動作はほとんどのアプリケーションで十分ですが、カスタムの乾燥が必要になる場合がいくつかありますが、次のような場合があります。

  • インク ストロークの大規模な、または複雑なコレクションのより効率的な管理
  • 大規模なインク キャンバスでのより効率的なパンとズームのサポート
  • z オーダーを維持しながら、インクやその他のオブジェクト (図形やテキストなど) をインターリーブする
  • インクを同期的に乾燥して DirectX 図形に変換します (たとえば、直線または図形をラスタライズし、別の InkCanvas レイヤーとしてではなく、アプリケーション コンテンツに統合します)。

カスタム乾燥には、インク入力を管理し、既定の InkCanvas コントロールではなく、ユニバーサル Windows アプリの Direct2D デバイス コンテキストにレンダリングするために、IInkD2DRenderer オブジェクトが必要です。

ActivateCustomDrying (InkCanvas が読み込まれる前) を呼び出すことによって、アプリは InkSynchronizer オブジェクトを作成して、インク ストロークを SurfaceImageSource または VirtualSurfaceImageSource にレンダリングする方法をカスタマイズします。

SurfaceImageSourceVirtualSurfaceImageSource はどちらも、アプリがアプリケーションのコンテンツに描画して作成するための DirectX 共有サーフェイスを提供しますが、VSIS には、パンとズームを実行するために画面よりも大きい仮想サーフェスが用意されています。 これらのサーフェスの表示更新は XAML の UI スレッドと同期されるため、インクをいずれかにレンダリングしたときに、同時にウェット インクが InkCanvas から取り除かれる場合があります。

SwapChainPanelに対してドライ インクをカスタムすることもできますが、UI スレッドとの同期は保証されず、インクが SwapChainPanel にレンダリングされてから InkCanvas からインクが削除されるまでに遅延が発生する可能性があります。

この機能の完全な例については、 Complex インクサンプルを参照してください。

Note

カスタム乾燥と インクツールバー
アプリが InkPresenter の既定のインク レンダリング動作をカスタムの乾燥実装でオーバーライドした場合、レンダリングされたインク ストロークは InkToolbar で使用できなくなり、InkToolbar の組み込みの消去コマンドは期待どおりに機能しなくなります。 消去機能を提供するには、すべてのポインター イベントを処理し、各ストロークでヒット テストを実行し、組み込みの "すべてのインクの消去" コマンドをオーバーライドする必要があります。

トピック 説明
インク ストロークを認識する 手書き認識を使用してインク ストロークをテキストに変換するか、カスタム認識を使用して図形に変換します。
インク ストロークを格納および取得する 埋め込みインク シリアル化形式 (ISF) メタデータを使用して、インク ストローク データをグラフィックス交換形式 (GIF) ファイルに格納します。
Windows 手描き入力アプリへの InkToolbar の追加 既定の InkToolbar を Windows アプリの手描き入力アプリに追加し、カスタム ペン ボタンを InkToolbar に追加して、そのカスタム ペン ボタンをカスタム ペン定義にバインドします。

API

サンプル

サンプルのアーカイブ