次の方法で共有


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

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

概要

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

InkCanvas には、StackPanelGrid コントロールなどの子要素のサイズを自動的に設定する要素の子でない限り、既定の Height プロパティと Width プロパティは 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 コントロールでインスタンス化されます。

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

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

標準のインク入力 (ペン先または消しゴムの先端/ボタンのいずれか) は、ペン バレル ボタン、マウスの右ボタン、または同様のメカニズムなど、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. 次に、インク ストロークとしてペンとマウスの両方からの入力データを解釈し、インクCanvas へのストロークのレンダリングに使用される初期インク ストローク属性を設定するように InkPresenter を構成します。

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

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

    最後に、InkPresenterStrokeStarted イベントと StrokesErased イベントのリスナーを割り当てます。 これらのイベントのハンドラーを使用して、新しいストロークが開始された場合、または既存のストロークが消去された場合に、選択 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. 次に、InkPresenter によって渡される未処理の PointerPressedPointerMovedPointerReleased イベントのハンドラーを定義します。

    これらのハンドラでは、ラッソーストロークや境界矩形など、すべての選択機能が実装されています。

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

      // 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 オブジェクトが必要です。

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

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

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

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

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

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

API(アプリケーションプログラミングインターフェース)

Samples

サンプルのアーカイブ