はじめに
手軽で賢い InkRecognizerContainer 手書き認識は、UWP で提供されています。
この InkRecognizerContainer を、Windows Forms / WPF - .NET Framework 4.8 から利用する手法を題材にしようと思います。
UWP - InkRecognizerContainer と WPF - InkCanvas のペアで利用したことがありますが、これは、正攻法ではないので、別記事にします。
まずは、UWP - InkRecognizerContainer と UWP - InkCanvas のペアで利用する方法を記載します。
今回利用する Windows Runtime(WinRT)組み込みサポートは .NET 5 で削除されたので、.NET Framework のみの情報とします。(.NET 5 以降は別手法です)
破壊的変更: WinRT の組み込みサポートは .NET から削除されています - .NET | Microsoft Learn
InkCanvas での入力検知(InkCanvas.InkPresenter.StrokesCollectedイベント)で、自動的に手書き認識させること、および、認識結果の複数候補を表示させることなどもできます。
簡潔なサンプルコードとするために、ボタンクリックで手書き認識して、第一候補のみ表示する形態とします。
テスト環境
ここに記載した情報/ソースコードは、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 以降で利用可能
InkRecognizerContainer は、InkAnalyzer と比較して、認識率が高く、使い勝手も良くなりました。
Windows 10 以降であれば、当然、InkRecognizerContainer を利用すべきです。
Windows Forms / WPF から、Windows Runtime を呼び出す手法が存在します。
今回は .NET Framework のみを対象としたので、Windows Runtime(WinRT)組み込みサポートで InkRecognizerContainer を利用します。
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 を利用します。
XAML Islands で UWP InkCanvas 利用
Windows Forms と WPF で手順が異なるので、それぞれの手順を以下に記載します。
Windows Forms
NuGet パッケージマネージャー コンソールで、NuGet Gallery | Microsoft.Toolkit.Forms.UI.Controls を導入します。
現時点での最新 Microsoft.Toolkit.Forms.UI.Controls 6.1.2 は、
System.Net.Http >= 4.0.0
という依存関係があります。
System.Net.Http が存在しない場合、System.Net.Http 4.0.0 を取り込みますが、4.0.0 は脆弱性が検知されています。
このため、最新の System.Net.Http を先に導入します。
PM> NuGet\Install-Package System.Net.Http
PM> NuGet\Install-Package Microsoft.Toolkit.Forms.UI.Controls
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 バージョンを記載します。
<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;
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 で各コントロールをレイアウトします。
<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;
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;
}
}