[WPF] 現在のフォーカスを安心してクリアする方法

2024-02-02 (金)

特定のタイミングでフォーカスを外す C# コードを説明します。
現在のフォーカスを外した際に、LostFocus イベントも発生させます。フォーカスを外した後のキーボードフォーカスも正しく設定します。

環境

  • .NET 8.0.100
  • C# 12.0
  • Visual Studio 2022 Version 17.8.5
  • Windows 11 Pro 22H2 22621.3007

結果

✨ (推奨) Window で実装する場合

public sealed class MyWindow : Window
{
    public void ResetFocus()
    {
        if (Keyboard.FocusedElement is DependencyObject focusedElement)
        {
            FocusManager.SetFocusedElement(FocusManager.GetFocusScope(focusedElement), null);
        }

        Focus();
    }
}

Window が確定しない場合

public static void ResetFocus()
{
    if (Keyboard.FocusedElement is not DependencyObject focusedElement)
        return;

    FocusManager.SetFocusedElement(FocusManager.GetFocusScope(focusedElement), null);

    // 必ず Window が見つかるケースで実行することを前提とします
    var window = focusedElement as Window ?? Window.GetWindow(focusedElement);
    window!.Focus();
}

説明

フォーカスを外すには、論理フォーカスとキーボードフォーカスの両方をクリアする必要があります。
フォーカスの概要 - WPF .NET Framework | Microsoft Learn

まずは、論理フォーカスをクリアします。これにより、フォーカスがあるコントロールの LostFocus イベントが発生するはずです。

// 例えば TextBox にフォーカスがある場合は、LostFocus イベントが発生するはずです
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(focusedElement), null);

次に、親ウィンドウにキーボードフォーカスを設定します。
親ウィンドウが分かっている場合や、次にフォーカスしたいコントロールが分かっている場合は、直接そのコントロールの Focus() を呼んでも良いと思います。

// 必ず Window が見つかるケースで実行することを前提とします
var window = focusedElement as Window ?? Window.GetWindow(focusedElement);
window!.Focus();

Window に設定したい理由は、注意点で説明します。

注意点

キーボードフォーカスをクリアする際に、Keyboard.ClearFocus() は使用しません。

理由

  • 本当に Keyboard.FocusedElementnull になってしまいます。
  • フォーカスを失うため、Window.KeyDown などのキーイベントも発生しなくなります⚠️
  • 通常 Window を表示すると Keyboard.FocusedElementWindow になるため、その動作に従います。

また、論理フォーカスとキーボードフォーカスの片方だけをクリアすると、正しくない動作になります。
例えば、TextBox にフォーカスがある状態で

  • FocusManager.SetFocusedElement() で論理フォーカスのみクリアすると、LostFocus が発生したのにキーカーソルが TextBox に残り、そのままキー入力が出来てしまう。
  • Keyboard.ClearFocus() でキーボードフォーカスのみクリアすると、LostFocus が発生せず、キーカーソルが TextBox から消えてしまう。

というような動作になってしまいます。

感謝

2024-02-02 (金)