USBバーコードリーダーの読み取り結果が、意図しない入力欄に入ってしまい困った経験はありませんか。WinForms(C#)ではWMIでデバイスを列挙できても、「特定USBデバイスの入力だけを特定TextBoxへ」OS側で直結するのは現実的ではありません。この記事では、確実にTextBoxで受け取る王道実装と、デバイス識別が必要な場合の選択肢を整理します。
USBバーコードリーダーが「TextBoxに入力される」仕組み
多くのUSBバーコードリーダーは、PCから見るとHIDキーボード(キーボードエミュレーション / キーボードウェッジ)として動作します。つまり、バーコードの内容を「キーボードで高速に文字入力した」のと同じ扱いでOSに送ります。
この性質により、OSは入力先を「いまフォーカスが当たっているコントロール」に決めます。WinFormsのTextBoxも例外ではなく、フォーカスがあればそこに入ります。
| よくある誤解 | 実際 | 結論 |
|---|---|---|
| 「USBバーコードリーダーの出力を、OSが特定TextBoxへ直接流せるはず」 | キーボードエミュレーションの場合、OSは“どのデバイスか”よりも“どこにフォーカスがあるか”で入力先を決める | TextBoxにフォーカスを維持し、イベントで処理するのが基本 |
| 「WMIでデバイスが特定できた=入力先も制御できる」 | WMIは列挙・情報取得が主用途で、入力のルーティング制御は担当外 | WMIでの解決は難しい |
最短で安定する王道パターン:バーコード用TextBox+フォーカス固定+Enterで確定
現場で最もトラブルが少ないのは、バーコード入力専用のTextBox(例:txtBarcode)を用意し、そこにフォーカスを当て続ける方法です。スキャナ側で「末尾にEnter(CR)」を送る設定にしておくと、確定処理が簡単になります。
基本実装(KeyPressでEnter確定)
まずは最も分かりやすい形です。Enterが来たら、TextBoxの内容を処理してクリアします。
private void txtBarcode_KeyPress(object sender, KeyPressEventArgs e)
{
// 多くのスキャナは末尾にCR(Enter)を送る
if (e.KeyChar == '\r')
{
var barcode = txtBarcode.Text;
if (!string.IsNullOrWhiteSpace(barcode))
{
ProcessBarcode(barcode);
}
txtBarcode.Clear();
// Enterの既定動作(ビープ音や次コントロール移動など)を抑止
e.Handled = true;
// 次の読取りも確実にここへ
BeginInvoke(new Action(() => txtBarcode.Focus()));
}
}
上記を使うなら、フォーム側で次の初期設定も入れておくと安定します。
private void Form1_Load(object sender, EventArgs e)
{
// IMEがONだと変換状態で入力が壊れることがあるため、バーコード欄は無効推奨
txtBarcode.ImeMode = ImeMode.Disable;
// バーコード長の上限が分かっているなら設定(事故入力対策にもなる)
txtBarcode.MaxLength = 64;
// 最初からフォーカス
txtBarcode.Focus();
}
フォーカス制御が「肝」になる理由
キーボードエミュレーションのスキャナは、入力先を選べません。選べるのはアプリ側で「受け取る場所にフォーカスを置く」ことです。つまり、次のような状況が起きると誤入力が発生します。
- オペレーターが別のTextBoxをクリックした
- ボタンにフォーカスが移った状態でスキャンした
- DataGridViewのセル編集中にスキャンした
- コンボボックスが開いた状態でスキャンした
そのため、バーコード運用画面では「スキャンは必ずここ」をUIとして守る設計が重要です。
「フォーカスが外れる」を現実的に潰す設計
単にtxtBarcode.Focus()を呼ぶだけでは、実運用で負ける場面があります。ここでは、よく効く対策を組み合わせて紹介します。
UI設計でフォーカスが移りにくくする
| 対策 | 狙い | 実装/設定例 |
|---|---|---|
| Tab順(TabIndex)を最適化 | 誤ってTab移動しても戻しやすくする | バーコード欄を先頭にする、不要なコントロールはTabStop=false |
| バーコード欄を目立たせる | ユーザーが迷わない | ラベルで「スキャンはこちら」など明示、背景色は運用で判断 |
| 入力が必要ない欄はReadOnly | 誤入力を減らす | ReadOnly=true、必要に応じてEnabled=false |
| 処理後に必ずFocusを戻す | 次スキャンの事故を防ぐ | BeginInvokeでFocus復帰 |
FormのKeyPreviewを使い、どこにフォーカスがあってもバーコード欄へ集約する
「ユーザー操作でフォーカスが移ってしまう」こと自体は完全には防げません。そこで、フォームが先にキーイベントを受け取るKeyPreviewをONにし、特定条件下ではバーコード欄へ誘導します。
public Form1()
{
InitializeComponent();
this.KeyPreview = true;
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
// 例:どこにいてもF9でバーコード欄へ戻す、など運用キーを用意する
if (e.KeyCode == Keys.F9)
{
txtBarcode.Focus();
e.Handled = true;
e.SuppressKeyPress = true;
return;
}
}
さらに踏み込むなら、「スキャン中らしさ」を検知してバーコード欄へ強制的に集める方法もあります(後述の“タイマーでスキャンを判定”)。
スキャナの末尾キーはEnterだけではない:CR/LF/Tabの違いを吸収する
バーコードリーダーの設定によって、末尾に付くキーが異なります。現場で混在しやすいのが「CR(Enter)」「LF(改行)」「Tab」です。アプリ側で吸収しておくと、機種変更や設定差分に強くなります。
| 末尾に送られがちなもの | WinFormsでの見え方 | 実務的な対処 |
|---|---|---|
| Enter(CR) | KeyPressで'\r' | Enterで確定処理(王道) |
| LF(改行) | KeyPressで'\n'が来る場合 | '\r'と'\n'の両方を確定扱いにする |
| Tab | KeyDownでKeys.Tab | Tab確定にするか、Tab移動を抑止して確定する |
例えば、CR/LFどちらでも確定するようにするなら、次のようにします。
private void txtBarcode_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
var barcode = txtBarcode.Text.Trim();
if (barcode.Length > 0)
{
ProcessBarcode(barcode);
}
txtBarcode.Clear();
e.Handled = true;
BeginInvoke(new Action(() => txtBarcode.Focus()));
}
}
Enterが来ない運用でも対応できる:タイマーで“スキャン入力”を判定する
機種や設定の都合で「末尾キーを付けられない」「付けたくない」ケースもあります。その場合は、入力の速度(短時間にまとまって入る)を利用して“スキャンらしさ”を判定し、一定時間入力が止まったら確定する方式が使えます。
キーボードで人が打つより、スキャナは圧倒的に速く連続入力するため、閾値(例:最後の入力から100~200ms無入力で確定)で見分けやすいのがポイントです。
private readonly Timer _scanTimer = new Timer();
private const int ScanIdleMs = 120; // 環境により調整
public Form1()
{
InitializeComponent();
_scanTimer.Interval = ScanIdleMs;
_scanTimer.Tick += (s, e) =>
{
_scanTimer.Stop();
CommitBarcodeIfNeeded();
};
txtBarcode.TextChanged += (s, e) =>
{
// 文字が追加されるたびにタイマーを延長する
_scanTimer.Stop();
_scanTimer.Start();
};
}
private void CommitBarcodeIfNeeded()
{
var barcode = txtBarcode.Text.Trim();
if (barcode.Length == 0) return;
ProcessBarcode(barcode);
txtBarcode.Clear();
txtBarcode.Focus();
}
この方式は便利ですが、次の注意点があります。
- 閾値(
ScanIdleMs)が短すぎると、長いバーコードで途中確定する恐れがある - 逆に長すぎると、スキャン後の反応が鈍く感じる
- キーボードで速く入力した場合も“スキャン扱い”される可能性があるため、バーコード欄を専用運用にするのが前提
バーコード入力TextBoxを「専用化」するテクニック
誤操作を減らすために、バーコード欄を“専用入力”として固めると運用が安定します。
IME・ショートカット・改行を抑止する
- ImeMode.Disable:変換で文字が壊れる事故を予防
- ShortcutsEnabled=false:Ctrl+Vなど貼り付け事故を予防(必要なら)
- Multiline=false:改行が混入しづらい
private void PrepareBarcodeTextBox()
{
txtBarcode.ImeMode = ImeMode.Disable;
txtBarcode.ShortcutsEnabled = false;
txtBarcode.Multiline = false;
}
「ビープ音が鳴る」「Enterでボタンが押される」を止める
フォームにAcceptButtonが設定されていると、Enterでボタンが反応してしまう場合があります。バーコード確定にEnterを使うなら、運用画面ではAcceptButtonの扱いも設計しましょう。
- バーコード欄でEnterを扱うときは、イベント内でHandled/SuppressKeyPressを適切に設定する
- 必要に応じてフォームのAcceptButtonを外す、または状況に応じて切り替える
private void txtBarcode_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
CommitBarcodeIfNeeded();
e.Handled = true;
e.SuppressKeyPress = true; // ビープや既定動作の抑止に効く
}
}
WMI(Win32_PnPEntity)でできること・できないこと
質問で挙がりやすいのが「WMIでUSBデバイスを絞り込めたのに、なぜTextBoxへ紐付けられないのか」という点です。結論から言うと、WMIはデバイスの情報を取得するための仕組みであり、入力をどこへ流すかという“ルーティング”を担いません。
| 項目 | WMIで可能? | 補足 |
|---|---|---|
| 接続中のUSB/HID機器を列挙する | 可能 | Win32_PnPEntityなどで情報取得できる |
| 機器名やVID/PIDっぽい情報で候補を絞る | 可能(ただし揺れあり) | デバイス名が変わる、ドライバや環境で表記差が出ることがある |
| 「そのデバイスのキー入力だけ」を特定TextBoxへ送る | 基本的に不可 | 入力処理は別レイヤー(Win32入力、HID、Raw Input等) |
つまり、WMIで「どのデバイスが接続されているか」は分かっても、WinFormsのKeyPressに流れてくる入力を「このデバイス由来だけ」に分離するのは別の手段が必要です。
「特定のUSBデバイスだけ」にこだわるなら選択肢はこの2つ
複数の入力デバイスがあり、通常キーボードの入力は別用途で使いつつ、バーコードスキャナの入力だけを分離したい場合、王道の選択肢は次のどちらかです。
メーカー提供のSDK/APIを使う
バーコードリーダーによっては、USBキーボードではなく、次のようなインターフェースを提供するモデルがあります。
- USB CDC(仮想COMポート)として受け取れる
- ネットワーク経由(Wi-Fi/有線)でソケット受信できる
- 専用ドライバ+SDKで読み取りイベントを受け取れる
この場合、読み取り結果は「キー入力」ではなく「データ受信」なので、任意のTextBoxに自由に代入できます。要件が厳しい現場(誤入力が許されない、複数入力デバイスがある、フォーカス依存を無くしたい)ほどSDK/COM方式が強いです。
Raw Input APIで物理デバイス単位に識別する(上級者向け)
WindowsのRaw Input APIを使うと、同じ“キーボード”として見える入力でもどのデバイス(ハンドル)から来たかを識別できます。これにより「スキャナ由来だけをバーコード欄へ」「通常キーボードは通常通り」などの制御が可能になります。
ただしRaw Inputは、WinFormsのKeyPressよりレイヤーが低く、次の難しさがあります。
- 受け取れるのはキーコード中心で、文字への変換(ToUnicode等)が必要になる場面がある
- 日本語配列/英語配列、修飾キー、NumPadなどで変換が複雑になる
- 実装・テストコストが高い(特に現場PCの環境差)
それでも「物理デバイスで分けたい」が必須なら、Raw Inputを検討します。WinFormsでの最低限の形(骨格)は次の通りです。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public partial class Form1 : Form
{
private const int WM_INPUT = 0x00FF;
public Form1()
{
InitializeComponent();
RegisterKeyboardRawInput();
}
private void RegisterKeyboardRawInput()
{
// UsagePage=0x01(汎用デスクトップ), Usage=0x06(キーボード)
var rid = new RAWINPUTDEVICE[]
{
new RAWINPUTDEVICE
{
usUsagePage = 0x01,
usUsage = 0x06,
dwFlags = RIDEV_INPUTSINK, // フォーカス外でも受けたい場合
hwndTarget = this.Handle
}
};
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICE))))
{
throw new System.ComponentModel.Win32Exception();
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_INPUT)
{
// ここでRAWINPUTを取得し、header.hDevice でデバイスを識別する
// スキャナのデバイスだけ拾って文字列化し、txtBarcodeへ流す
}
base.WndProc(ref m);
}
private const int RIDEV_INPUTSINK = 0x00000100;
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTDEVICE
{
public ushort usUsagePage;
public ushort usUsage;
public int dwFlags;
public IntPtr hwndTarget;
}
[DllImport("User32.dll", SetLastError = true)]
private static extern bool RegisterRawInputDevices(
RAWINPUTDEVICE[] pRawInputDevices,
uint uiNumDevices,
uint cbSize);
}
上記はあくまで入り口です。実際には、WM_INPUTでRAWINPUTを取り出し、デバイス名(VID/PIDなど)を照合して“スキャナだけ”を判定し、キー入力を文字列に組み立てる必要があります。要件がそこまで厳しくないなら、まずはフォーカス固定+確定キーで設計する方が、納期・保守・安定性の面で得になるケースが多いです。
どの方法を選ぶべきか:要件別のおすすめ
| 方式 | デバイス識別 | フォーカス依存 | 実装難度 | おすすめの場面 |
|---|---|---|---|---|
| バーコード用TextBox+Enter確定 | 不可 | あり | 低 | 単一スキャナ運用、画面がバーコード中心、最短で安定させたい |
| TextChanged+タイマー確定 | 不可 | あり | 中 | 末尾キーが付けられない、設定が揃わない、運用都合でEnterを避けたい |
| メーカーSDK/仮想COMで受信 | 可能 | なし(設計次第) | 中〜高 | 誤入力を絶対避けたい、フォーカスに依存したくない、拡張性重視 |
| Raw Input APIで識別 | 可能 | なし(設計次第) | 高 | 複数キーボードが同居、スキャナだけ分離必須、OSレベルで拾いたい |
現場でハマりやすいポイントと対処チェックリスト
読取り結果に余計な文字が混ざる
- 末尾のCR/LF/Tabが混ざる:Trim()、確定キーの吸収を入れる
- プレフィックス/サフィックスが付いている:スキャナ設定を確認し、アプリ側で除去する
- 特定文字(ハイフン、スラッシュ等)が違う:キーボードレイアウト設定や、スキャナの言語設定を確認
スキャンすると別コントロールに入ってしまう
- 処理後にFocusを戻していない:BeginInvokeでtxtBarcode.Focus()を徹底
- ユーザーがクリックで移動している:バーコード欄を目立たせる、運用ルール化、TabStopの見直し
- DataGridView編集中:編集開始を抑止するか、スキャン中は編集をロックする設計を検討
Enterでビープが鳴る/ボタンが押される
- KeyDown側で
SuppressKeyPressを使う - AcceptButtonの影響を見直す(運用画面はAcceptButtonを外す/切り替える)
日本語入力(IME)で入力が壊れる
- バーコード欄だけはImeMode.Disableを基本にする
- 英数字専用運用なら、TextBoxの検証(半角英数のみ許可)も入れる
まとめ:まずは「フォーカス設計」で勝ち、必要なら低レベルへ
- USBバーコードリーダーの多くはキーボードとして入力するため、入力先は基本的にフォーカスで決まる
- WinFormsでは、バーコード専用TextBoxを用意し、Focus固定+Enter(またはタイマー)で確定するのが最も現実的で安定する
- WMI(Win32_PnPEntity)はデバイス列挙に有効だが、入力を特定TextBoxへ直結する用途には向かない
- 物理デバイス単位で区別が必須なら、メーカーSDK/仮想COMかRaw Input APIを検討する

コメント