USBバーコードリーダーの入力を特定TextBoxに紐付ける方法|WinForms(C#)で確実に受け取る実装

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'の両方を確定扱いにする
TabKeyDownでKeys.TabTab確定にするか、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を検討する

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次