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 をテンプレ化しておくのが、最もコスト対効果の高い予防策です。

コメント