WinFormsにホストしたWPFでキーボード入力が反映されない原因と解決策|ElementHost.EnableModelessKeyboardInterop

WinForms(Windowsフォーム)アプリに ElementHost を置き、WPF の UserControl を表示する構成は、既存資産を活かしながら段階的にUIを刷新できる反面、「WPFウィンドウをモードレス(Show())で開いたら TextBox に文字が入らない」といった入力系の落とし穴に遭遇しがちです。本記事では、キーボードイベントは発生しているのに見た目が更新されない原因と、現場で使える最短の解決策を整理します。

目次

発生する問題:WinFormsにホストしたWPFから開いたWPFウィンドウで文字入力が反映されない

今回の症状は、次のような混在構成で起きやすい典型例です。

  • WinForms アプリ内のユーザーコントロール上に ElementHost を配置
  • ElementHost の中に WPF の UserControl(例:WpfControl)を表示
  • その WPF 側のボタンから、別の WPF ウィンドウ(例:MainWindow)を window.Show() でモードレス表示
  • MainWindow には TextBox があり、キーイベント(KeyDown 等)は発生するのに、文字や数字を入力してもTextBoxの表示が更新されず入力が反映されない

一見すると「フォーカスが当たっていない」「バインディングがおかしい」「TextBoxがReadOnlyになっている」などを疑いたくなります。しかし今回のケースでは、フォーカスやイベントの問題ではなく、WinFormsとWPFの入力処理の“橋渡し”が不足している可能性が高いです。

よくある見え方(症状の特徴)

見える現象起きていること勘違いしやすい原因
KeyDown / PreviewKeyDown は発生するキー自体の通知は来ている「TextBox側のイベント処理が間違い」
TextBoxにカーソルがあるのに文字が増えない文字入力(テキスト合成)が反映されていない「IMEやキーボードレイアウトの問題」
数字やアルファベットを押しても表示が変わらないWPFの入力システム側が適切に回っていない「描画更新(Invalidate)の問題」
ShowDialog() に変えると直ることがあるモーダル時はメッセージループが別挙動「モーダルなら安全、モードレスは危険(理由不明)」

原因:WinFormsのメッセージループとWPFの入力処理がモードレス表示で噛み合わない

WinForms と WPF はどちらもWindows上のGUIですが、内部の入力処理の考え方が異なります。

  • WinForms:Win32メッセージ(WM_KEYDOWNなど)をメインのメッセージループで処理してコントロールへ配布
  • WPF:Win32メッセージを土台にしつつ、さらに独自の入力システム(InputManager / TextComposition など)でテキスト入力やIME、ルーティングイベントを実現

ElementHost を使うと、WinFormsの中でWPFのコンテンツを表示できます。ただし、WPFのコンテンツからさらに別のWPFウィンドウを「モードレス(Show)」で開くと、WinForms側のメッセージループとWPF側の入力処理の連携がデフォルト状態では不十分になり、WPFウィンドウが「キーボード入力を正しくテキストとして反映できない」状態に陥ることがあります。

重要なのは、キーイベントが発生している=文字入力が成立している、ではないという点です。WPFのTextBoxが文字を表示できるまでには、キー入力を“文字入力”として解釈し、テキスト合成(IME含む)を行い、TextBox内部の編集状態を更新し、描画に反映する、という段階があります。混在環境ではこの流れを支えるメッセージ処理が欠けると、イベントは来ているのに見た目が更新されない、という不思議な状況が起こり得ます。

解決策:ElementHost.EnableModelessKeyboardInterop を Show() 前に呼ぶ

結論として、WinFormsにホストされたWPFコントロールからモードレスなWPFウィンドウを表示する場合は、表示前に ElementHost.EnableModelessKeyboardInterop(window) を呼び出し、WinFormsとWPF間のキーボード入力の相互運用(Interop)を有効化します。

最小修正のコード例(クリックでWPFウィンドウを開く直前に追加)

質問の構成に近い形で、修正点が最も少ない例です。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.Integration; // ★これが必要

public partial class WpfControl : UserControl
{
    public WpfControl()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        var window = new MainWindow();

        // ★ WinForms と WPF 間のキーボード入力連携を有効にする(Showの前)
        ElementHost.EnableModelessKeyboardInterop(window);

        window.Show();
    }
}

この1行で、TextBoxへの文字入力がUIに反映されるようになるケースが非常に多いです。

なぜこれで直るのか(現場で役立つ理解)

EnableModelessKeyboardInterop は、WinForms主導のメッセージループの中で、WPF側が必要とするキーボード関連のメッセージ処理が適切に行われるよう“橋渡し”を追加します。結果として、モードレスで開いたWPFウィンドウでも、WPFの入力システムが成立し、TextBoxが通常通りにテキスト編集を行えるようになります。

逆に言うと、混在環境でのモードレス表示は「WPFだけで完結するアプリ」と同じ前提が崩れるため、意図的にInteropを有効にする必要がある、ということです。

Show() と ShowDialog() で挙動が変わる理由

現場では「ShowDialogにしたら直った」という話もよく出ます。これは偶然ではなく、ウィンドウの表示形態によりメッセージループの挙動が変わるためです。

表示方法特徴混在環境での入力トラブル対処の基本
Show()(モードレス)呼び出し元に制御が戻り、複数ウィンドウ操作が可能発生しやすい(今回のパターン)EnableModelessKeyboardInterop をShow前に呼ぶ
ShowDialog()(モーダル)ダイアログが閉じるまで呼び出し元が待機相対的に発生しにくい(ただしゼロではない)基本は不要だが、複雑な入力制御がある場合は状況次第

ただし、UX的にモードレスが必要な画面(ツールウィンドウ、設定パネル、非同期操作の進捗など)も多いため、「ShowDialogに逃げる」よりも、Show()を維持したまま正攻法でInteropを有効化するのが実務ではおすすめです。

実務での追加改善:Owner を設定してフォーカスとZオーダーを安定させる

キーボード入力の問題は EnableModelessKeyboardInterop で解決できることが多い一方で、混在構成では「背面に回る」「Alt+Tabの挙動が気持ち悪い」「親フォームの最小化に追従しない」といったウィンドウ関係の不満が出ることがあります。

その場合は、WPFウィンドウの Owner(親ハンドル)を WinForms 側に設定すると安定します。WinFormsのフォームから開く形に寄せられるなら、次の実装が扱いやすいです。

using System.Windows.Forms.Integration;
using System.Windows.Interop;

// WinFormsのFormなどから呼ぶ想定
private void OpenWpfWindowModeless()
{
    var window = new MainWindow();

    // キーボード相互運用(Show前)
    ElementHost.EnableModelessKeyboardInterop(window);

    // OwnerをWinFormsフォームに設定(Zオーダーや最小化連動が安定)
    new WindowInteropHelper(window).Owner = this.Handle;

    window.Show();
    window.Activate();
}

WPFコントロール側のボタンから直接開いている場合でも、設計としては「WPF側は“開いてほしい”ことだけ通知し、実際に開くのはWinForms側(Ownerも設定)」に寄せると、運用・保守が楽になります。

WPFコントロール→WinFormsに“開いて”を通知する設計例

混在アプリでありがちな“責務の境界”を整理する方法です。WPF側はUI部品として完結させ、ウィンドウ管理はWinForms側で統一します。

// WPF UserControl 側(WpfControl)
public partial class WpfControl : UserControl
{
    public event EventHandler RequestOpenMainWindow;

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        RequestOpenMainWindow?.Invoke(this, EventArgs.Empty);
    }
}
// WinForms 側(ElementHostを持つ側)
private void Setup()
{
    var wpfControl = new WpfControl();
    wpfControl.RequestOpenMainWindow += (_, __) =>
    {
        var window = new MainWindow();
        ElementHost.EnableModelessKeyboardInterop(window);
        new System.Windows.Interop.WindowInteropHelper(window).Owner = this.Handle;
        window.Show();
        window.Activate();
    };

    elementHost1.Child = wpfControl;
}

この方式にすると、ウィンドウ表示のルール(Owner設定、位置、最前面化、二重起動防止など)をWinForms側に集約でき、機能追加時の事故が減ります。

呼び出しタイミングが重要:Show() の前でないと効かない

EnableModelessKeyboardInterop の注意点はシンプルですが重要です。ウィンドウを表示した後に呼んでも効果が出ないことがあります。

タイミング例結果
正しいEnableModelessKeyboardInterop(window); window.Show();入力が反映されやすい
避けたいwindow.Show(); EnableModelessKeyboardInterop(window);直らない/環境依存になりやすい

また、ウィンドウを毎回 new して開くなら「開くたびに」呼びます。ウィンドウを使い回す設計でも、基本は「対象のモードレスWPFウィンドウに対して有効化されている状態」を保証するのが安全です。

チェックリスト:まだ直らないときに見るポイント

多くのケースは1行追加で解消しますが、現場では別要因が重なっていることもあります。以下の順で切り分けると、遠回りしにくいです。

確認項目よくある落とし穴対処例
EnableModelessKeyboardInterop を Show 前に呼んでいるか呼んでいるつもりで Show 後になっている生成直後(Show直前)に移動
表示しているのは Show()(モードレス)か別経路で ShowDialog / Show の混在問題の経路を固定して確認
TextBox がフォーカスを取れているかフォーカスが別要素に奪われているwindow.Activate()、必要なら初期フォーカス設定
UIスレッドが1本で動いているか別STAスレッドでWPFを動かしているスレッド設計を見直す(入力/Dispatcherが複雑化)
IMEやショートカットキーが期待通りかIME変換が反映されない/Alt系が効かないOwner設定、フォーカス、ショートカット処理の重複を確認

「KeyDownは来るのに文字が出ない」場合に見るべきイベント

デバッグで確認するなら、WPF側では KeyDown だけでなく TextInput(またはIME絡み)も追うと原因がはっきりします。キー入力は来ているがテキスト入力が成立していない場合、Interop不足の可能性がさらに濃くなります。

運用のコツ:混在アプリでは“ウィンドウをどこで開くか”を決める

WinForms+WPFの混在は、画面を増やすほど「どっちが主導権を持つか」が曖昧になりがちです。特に、WPF側から自由に Window を開き始めると、次のような問題が連鎖します。

  • Owner未設定で背面に回る/最小化連動しない
  • 入力(キーボード、IME、アクセラレータ)が環境依存になる
  • 多重起動やライフサイクル管理(閉じるタイミング)が破綻する

おすすめは次のどちらかに寄せることです。

  • WinForms主導に寄せる:WPFは「表示部品」と割り切り、ウィンドウ管理はWinForms側で統一(Owner/Interop/二重起動防止も集約)
  • WPF主導に寄せる:将来的にWPFへ移行するなら、ウィンドウ管理をWPF側へ寄せ、WinFormsはホストの役割に限定する(ただし移行期間の設計は必要)

今回のような入力不具合は、「主導権が混在したまま、モードレスウィンドウを増やしていく」状況で再発しやすいです。最初にルールを決め、EnableModelessKeyboardInterop を“混在アプリの標準手順”にしておくと、後からの手戻りが減ります。

まとめ:WinFormsホストのWPFでモードレスWPFウィンドウを開くならInteropを標準化する

  • WinFormsの ElementHost 配下のWPFから、別のWPFウィンドウを Show() で開くと、TextBoxの入力が反映されないことがある
  • 原因は、WinFormsのメッセージループとWPFの入力処理がモードレス表示で噛み合わないこと
  • 解決策は ElementHost.EnableModelessKeyboardInterop(window) を Show() の前に呼ぶ
  • 実務では、Owner設定(WindowInteropHelper)や「ウィンドウ管理の集約」も併用すると安定する

混在環境は「動いているように見える」状態が長く続く一方で、入力・フォーカス・ウィンドウ関係の不具合はユーザー体験に直撃します。モードレスWPFウィンドウを開く箇所では、EnableModelessKeyboardInterop をテンプレ化しておくのが、最もコスト対効果の高い予防策です。

この記事を書いた人

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

コメント

コメントする

目次