✍️

C# - InkRecognizerContainer - Windows FormsとWPFでの利用

2025/01/22に公開

はじめに

手軽で賢い InkRecognizerContainer 手書き認識は、UWP で提供されています。
この InkRecognizerContainer を、Windows Forms / WPF - .NET Framework 4.8 から利用する手法を題材にしようと思います。

UWP - InkRecognizerContainer と WPF - InkCanvas のペアで利用したことがありますが、これは、正攻法ではないので、別記事にします。
まずは、UWP - InkRecognizerContainer と UWP - InkCanvas のペアで利用する方法を記載します。

テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。

  • Windows Forms - .NET Framework 4.8
  • WPF - .NET Framework 4.8

前準備

Microsoft.Windows.SDK.Contracts、Microsoft.Toolkit.Forms.UI.Controls、Microsoft.Toolkit.Wpf.UI.Controls の何れかを導入すると、Microsoft.VCRTForwarders.140 が、いつの間にか、参照に追加されてしまうようです。
Microsoft.VCRTForwarders.140 は、プラットフォームが「Any CPU」だと、ビルド時に警告が出力されます。

このため、ソリューション、プロジェクトを作成したら、まず、下記手順で、プラットフォームを「x86」にします。

ソリューション 構成マネージャーを開いて、プラットフォーム「新規作成」を選択します。

新しいプラットフォーム「x86」を選択して作成します。

次は、プラットフォーム「編集」を選択します。

「Any CPU」を削除してください。

手書き認識

Microsoft が Windows として提供している、手書き認識には、下記が存在します。

  • InkAnalyzer
    • SDK for Vista として提供された System.Windows.Ink.InkAnalyzer(IACore.dll, IALoader.dll, IAWinFX.dll)
  • InkRecognizerContainer
    • Windows 10 で UWP 用に提供された Windows.UI.Input.Inking.InkRecognizerContainer
    • Windows 10 以降で利用可能

https://learn.microsoft.com/ja-jp/uwp/api/windows.ui.input.inking.inkrecognizercontainer

InkRecognizerContainer は、InkAnalyzer と比較して、認識率が高く、使い勝手も良くなりました。
Windows 10 以降であれば、当然、InkRecognizerContainer を利用すべきです。

Windows Forms / WPF から、Windows Runtime を呼び出す手法が存在します。
今回は .NET Framework のみを対象としたので、Windows Runtime(WinRT)組み込みサポートで InkRecognizerContainer を利用します。

https://learn.microsoft.com/ja-jp/windows/apps/desktop/modernize/desktop-to-uwp-enhance

Windows Runtime(WinRT)組み込みサポート

Windows Forms/WPF から、Windows Runtime(WinRT)組み込みサポートを利用する場合、下記手順が必要となります。

  • Windows SDK 導入
    • Windows SDK | Microsoft Developer から、ターゲットプラットフォームに該当する Windows SDK を入手します。今回は最新のWindows SDK(10.0.26100.0)を利用
  • Windows.winmd 参照追加
    • ソリューションエクスプローラ「参照の追加」で、C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0\Windows.winmd を追加
  • NuGet パッケージマネージャー コンソールで、NuGet Gallery | Microsoft.Windows.SDK.Contracts を導入
    • PM> NuGet\Install-Package Microsoft.Windows.SDK.Contracts

手書き入力コントール

手書き入力コントールには、下記が存在します。

  • Windows Forms
    • InkEdit
    • InkEdit ととして手書き認識機能あり
  • WPF
    • System.Windows.Controls.InkCanvas
    • InkAnalyzer とペアで利用
  • UWP
    • Windows.UI.Xaml.Controls.InkCanvas
    • InkRecognizerContainer とペアで利用

Windows Forms / WPF から、UWP コントロールを利用する手法として、XAML Islands があります。
InkRecognizerContainer を選択したので、XAML Islands で、UWP - InkCanvas を利用します。

https://learn.microsoft.com/ja-jp/windows/apps/desktop/modernize/xaml-islands/xaml-islands

https://learn.microsoft.com/ja-jp/windows/communitytoolkit/controls/wpf-winforms/inkcanvas

XAML Islands で UWP InkCanvas 利用

Windows Forms と WPF で手順が異なるので、それぞれの手順を以下に記載します。

Windows Forms

NuGet パッケージマネージャー コンソールで、NuGet Gallery | Microsoft.Toolkit.Forms.UI.Controls を導入します。

PM> NuGet\Install-Package System.Net.Http
PM> NuGet\Install-Package Microsoft.Toolkit.Forms.UI.Controls

https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.toolkit.forms.ui.controls.inkcanvas

WPF

NuGet パッケージマネージャー コンソールで、NuGet Gallery | Microsoft.Toolkit.Wpf.UI.Controls を導入します。

PM> NuGet\Install-Package Microsoft.Toolkit.Wpf.UI.Controls

app.manifest

ターゲットを、app.manifest で明示します。

ソリューションエクスプローラ「追加」「新しい項目」で「アプリケーションマニフェストファイル(Windowsのみ)」を選択します。
追加した app.manifest の Windows 10 定義部分のコメントを外して、maxversiontested で Windows SDK バージョンを記載します。

app.manifest
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Windows 10 -->
    <maxversiontested Id="10.0.26100.0"/>
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  </application>
</compatibility>

サンプルコード

前述、「Windows runtime (WinRT) 組み込みサポート」「XAML Islands で UWP InkCanvas 利用」「app.manifest」に記載した手順を実施した上でのサンプルコードを記載します。

Windows Forms - .NET Framework 4.8

using Windows.UI.Input.Inking;
using Microsoft.Toolkit.Forms.UI.Controls;
Form1.cs
public partial class Form1 : Form
{
  // 動的配置コントール
  private InkCanvas inkCanvas = null;
  private TextBox txtResult = null;

  public Form1()
  {
    InitializeComponent();

    // フォームサイズ
    this.Width = 440;
    this.Height = 290;

    // 手書きコントロール配置
    inkCanvas = new InkCanvas
      { Width = 400, Height = 200, Top = 10, Left = 10, Name = "inkCanvas" };
    this.Controls.Add(inkCanvas);
    // マウス・ペン・タッチ入力
    inkCanvas.InkPresenter.InputDeviceTypes = 
      Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Mouse |
      Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Pen |
      Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Touch;

    // 認識結果テキストボックス
    txtResult = new TextBox
      { Width = 300, Height = 20, Top = 220, Left = 110, Name = "txtResult" };
    this.Controls.Add(txtResult);

    // 認識ボタン
    var btnAction = new Button
      { Width = 50, Height = 20, Top = 220, Left = 10, Text = "認識", 
        Name = "btnAction" };
    this.Controls.Add(btnAction);
    btnAction.Click += btnAction_Click;

    // 消去ボタン
    var btnClear = new Button
      { Width = 50, Height = 20, Top = 220, Left = 60, Text = "消去", 
        Name = "btnAction" };
    this.Controls.Add(btnClear);
    btnClear.Click += (sender, e) => {
      inkCanvas.InkPresenter.StrokeContainer.Clear();
      txtResult.Text = string.Empty;
    };
  }

  private async void btnAction_Click(object sender, EventArgs e)
  {
    // 手書き認識結果クリア 
    txtResult.Text = string.Empty;

    // 手書きストローク確認
    var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
    if (strokes != null && strokes.Count > 0)
    {
      var irc = new InkRecognizerContainer();
      // 手書き認識 日本語をデフォルトに設定
      var reco = irc.GetRecognizers().FirstOrDefault(r => r.Name.Contains("日本語"));
      if (reco != null)
      {
        irc.SetDefaultRecognizer(reco);
      }

      // 手書き認識結果取得
      IReadOnlyList<InkRecognitionResult> results =
        await irc.RecognizeAsync(
          inkCanvas.InkPresenter.StrokeContainer,
          InkRecognitionTarget.All);
      if (results != null && results.Count > 0)
      {
        // 第一候補のみ取得
        IReadOnlyList<string> candidates = results[0].GetTextCandidates();
        if (candidates != null && candidates.Count > 0)
        {
          txtResult.Text = candidates[0];
        }
      }
    }
  }
}

WPF - .NET Framework 4.8

xmlns:toolkit として、Microsoft.Toolkit.Wpf.UI.Controls を定義して、Grid で各コントロールをレイアウトします。

MainWindow.xaml
<Window x:Class="MyApp.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:toolkit="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:local="clr-namespace:MyApp"
  mc:Ignorable="d"
  Title="MainWindow" Height="280" Width="420">
  <Grid Margin="10,10,0,0">
    <Grid.RowDefinitions>
      <RowDefinition Height="200"/>
      <RowDefinition Height="20"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="50"/>
      <ColumnDefinition Width="50"/>
      <ColumnDefinition Width="300"/>
    </Grid.ColumnDefinitions>
    <toolkit:InkCanvas x:Name="inkCanvas" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"/>
    <Button x:Name="btnAction" Grid.Row="1" Grid.Column="0" Content="認識" Click="btnAction_Click" />
    <Button x:Name="btnClear" Grid.Row="1" Grid.Column="1" Content="消去" Click="btnClear_Click" />
    <TextBlock x:Name="txtResult" Grid.Row="1" Grid.Column="2" />
  </Grid>
</Window>
using Windows.UI.Input.Inking;
using Microsoft.Toolkit.Wpf.UI.Controls;
MainWindow.xaml.cs
public partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();

    // マウス・ペン・タッチ入力
    inkCanvas.InkPresenter.InputDeviceTypes =
      Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Mouse |
      Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Pen |
      Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Touch;
  }

  // 手書き認識
  private async void btnAction_Click(object sender, RoutedEventArgs e)
  {
    // 手書き認識結果クリア 
    txtResult.Text = string.Empty;

    // 手書きストローク確認
    var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
    if (strokes != null && strokes.Count > 0)
    {
      var irc = new InkRecognizerContainer();
      // 手書き認識 日本語をデフォルトに設定
      var reco = irc.GetRecognizers().FirstOrDefault(r => r.Name.Contains("日本語"));
      if (reco != null)
      {
        irc.SetDefaultRecognizer(reco);
      }

      // 手書き認識結果取得
      IReadOnlyList<InkRecognitionResult> results =
        await irc.RecognizeAsync(
          inkCanvas.InkPresenter.StrokeContainer,
          InkRecognitionTarget.All);
      if (results != null && results.Count > 0)
      {
        // 第一候補のみ取得
        IReadOnlyList<string> candidates = results[0].GetTextCandidates();
        if (candidates != null && candidates.Count > 0)
        {
          txtResult.Text = candidates[0];
        }
      }
    }
 }

  // クリア
  private void btnClear_Click(object sender, RoutedEventArgs e)
  {
    inkCanvas.InkPresenter.StrokeContainer.Clear();
    txtResult.Text = string.Empty;
  }
}

出典

本記事は、2025/01/21 Qiita 投稿記事の転載です。

C# - InkRecognizerContainer - Windows FormsとWPFでの利用

Discussion