15
11

【.NET】フォームを強制的にアクティブにする

Last updated at Posted at 2019-05-26

はじめに

工場など使用する場合、PCを単一のアプリケーション専用にすることがあります。スタートアップに専用のアプリケーションで登録して即使用(入力)できるようにしたいわけです。

Windows 10にてスタートアップからアプリケーション起動後にテキストボックスに入力出来るようになっているはずなのですが、入力を受け付けません。
見た目はアクティブでテキストボックス上はキャレットが点滅しているが入力ができないという状態です。ただ、厄介なことに毎回発生するわけではないです。

原因

タスクバーでアイコンが点滅した状態になっており、アプリケーションが正常にアクティブになっていないため。
アプリケーションをマウスクリック等でアクティブにすれば入力ができるようになります。

OS起動後にメニューからやexeのダブルクリックなどでアプリケーションを起動する上では問題が発生しないので、スタートアップ登録による起動からだと何かしらの影響により発生してしまうようです。

暫定対応

exeのプロパティにて「管理者としてこのプログラムを実行する」にチェックを付けると回避できることを同僚が見つけました。

恒久対応

出来るならプログラムで何とか回避したい。
以前の記事「【.NET】ウインドウを一時的に最前面に表示しフォーカスを奪う」で、強制的にアクティブにしたことがあったので、これを採用しました。

フォーム起動時にForceActiveWindow()を呼んで強制的にアクティブにします。

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);

/// <summary>
/// Windowフォームアクティブ化処理
/// </summary>
/// <param name="handle">フォームハンドル</param>
/// <returns>true : 成功 / false : 失敗</returns>
private static bool ForceActive(IntPtr handle)
{
    const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
    const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
    const int SPIF_SENDCHANGE = 0x2;

    IntPtr dummy = IntPtr.Zero;
    IntPtr timeout = IntPtr.Zero;

    bool isSuccess = false;

    int processId;
    // フォアグラウンドウィンドウを作成したスレッドのIDを取得
    int foregroundID = GetWindowThreadProcessId(GetForegroundWindow(), out processId);
    // 目的のウィンドウを作成したスレッドのIDを取得
    int targetID = GetWindowThreadProcessId(handle, out processId);

    // スレッドのインプット状態を結び付ける
    AttachThreadInput(targetID, foregroundID, true);

    // 現在の設定を timeout に保存
    SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, timeout, 0);
    // ウィンドウの切り替え時間を 0ms にする
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, dummy, SPIF_SENDCHANGE);

    // ウィンドウをフォアグラウンドに持ってくる
    isSuccess = SetForegroundWindow(handle);

    // 設定を元に戻す
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, timeout, SPIF_SENDCHANGE);

    // スレッドのインプット状態を切り離す
    AttachThreadInput(targetID, foregroundID, false);

    return isSuccess;
}

Windowフォームの場合

WinForm
/// <summary>
/// Windowフォームアクティブ化処理
/// </summary>
private void ForceActiveWindow()
{
    // タスクバーが点滅しフォーカスはあるのに入力できない状態になるため
    for (int i = 0; i < 3; i++)
        if (ForceActive(this.Handle)) break;
}

Shownイベントで、ForceActiveWindow()を呼び出す。

WPFの場合

WPF
/// <summary>
/// WPFアクティブ化処理
/// </summary>
private void ForceActiveWindow()
{
    var helper = new System.Windows.Interop.WindowInteropHelper(this);
    // タスクバーが点滅しフォーカスはあるのに入力できない状態になるため
    for (int i = 0; i < 3; i++)
        if (ForceActive(helper.Handle)) break;
}

FormではShownイベントにあたるContentRenderedイベントで呼び出す。

public MainWindow()
{
    // ウィンドウのコンテンツが描画された後のイベント
    ContentRendered += (s, e) => {
        // 最前面表示
        ForceActiveWindow();
    };

最後に

Windows 7 からWindows 10 に移行する上で問題になりうる一つです。検証する際にチェック対象にしておくといいかも知れませんね。

15
11
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11