本文へ移動
vast-cowのブログ
前のページへ戻る

指定したHWNDのウィンドウをアクティブにする

この記事を編集

Windowsアプリケーションでは、特定のウィンドウを前面に出して操作対象にしたい場合があります。 このコードは、指定した HWND のウィンドウをアクティブにし、可能であればフォアグラウンドウィンドウとして表示するためのものです。

目的

この関数の目的は、指定されたウィンドウをユーザーが操作できる状態にすることです。

たとえば、別のウィンドウに隠れているアプリケーションを前面に出したい場合や、最小化されているウィンドウを復元して表示したい場合に使います。

使い方

主に使う関数は activate_window です。

bool result = activate_window(hwnd);

hwnd には、アクティブにしたいウィンドウのハンドルを渡します。

処理が成功して、そのウィンドウがフォアグラウンドになった場合は true を返します。 失敗した場合は false を返します。

処理の流れ

このコードでは、まず指定された HWND をトップレベルウィンドウに変換します。 子ウィンドウの HWND が渡された場合でも、親となる最上位のウィンドウを対象にするためです。

次に、対象のウィンドウが存在するか、表示可能かを確認します。 すでに対象ウィンドウが前面にある場合は、そのまま成功として true を返します。

最小化されている場合

対象ウィンドウが最小化されている場合は、ShowWindowAsync を使って復元します。

ShowWindowAsync(hwnd, SW_RESTORE);

最小化されていない場合は、通常の表示状態にします。

ShowWindowAsync(hwnd, SW_SHOW);

これにより、隠れていたウィンドウや最小化されていたウィンドウを表示できるようにします。

前面に出す処理

ウィンドウを表示した後、次のような処理で前面に出します。

SetWindowPos(...);
BringWindowToTop(hwnd);
SetForegroundWindow(hwnd);
SetActiveWindow(hwnd);
SetFocus(hwnd);

これらを組み合わせることで、指定したウィンドウをできるだけ確実に前面へ移動し、入力フォーカスを与えようとします。

AttachThreadInputの利用

Windowsでは、別スレッドのウィンドウを直接アクティブにできない場合があります。 そのため、このコードでは AttachThreadInput を使い、自分のスレッドと対象ウィンドウのスレッドを一時的に関連付けています。

これにより、SetForegroundWindow や SetFocus が成功しやすくなります。

処理後は、関連付けを解除しています。

AttachThreadInput(current_tid, target_tid, FALSE);

一時的に接続し、必要な処理が終わったら戻す、という使い方です。

ALTキー入力によるフォールバック

通常の方法で前面に出せなかった場合、このコードでは ALT キー入力を一度送信します。

tap_alt_key();

Windowsでは、ALTキー入力の後に SetForegroundWindow が成功しやすくなる場合があります。 特に UWP アプリや ApplicationFrameHost が関係するケースで有効なことがあります。

その後、もう一度ウィンドウを表示し、前面に出す処理を試します。

戻り値

activate_window は、最後に対象ウィンドウがフォアグラウンドになっているか確認します。

return is_foreground(hwnd);

成功していれば true、失敗していれば false を返します。

利用例

この関数は、次のような場面で利用できます。

注意点

Windowsには、アプリケーションが自由に他のウィンドウを前面に出せないようにする制限があります。 そのため、このコードでも必ず成功するとは限りません。

ただし、トップレベルウィンドウの正規化、スレッド入力の接続、最小化状態の復元、ALTキー入力によるフォールバックを組み合わせることで、実用上成功しやすい構成になっています。

まとめ

このコードは、指定した HWND のウィンドウをアクティブにするための実用的な関数です。

単に SetForegroundWindow を呼ぶだけではうまくいかない場合に備え、ウィンドウの状態確認、復元、スレッド入力の接続、フォールバック処理を含んでいます。

Windows上で特定のウィンドウを前面に出したい場合に、扱いやすい補助関数として利用できます。

#define WIN32_LEAN_AND_MEAN 
#include <windows.h> 
#include <stdbool.h> 
 
static HWND normalize_top_level(HWND hwnd) 
{ 
    HWND root; 
 
    if (!hwnd) return NULL; 
 
    root = GetAncestor(hwnd, GA_ROOT); 
    if (root && IsWindow(root)) return root; 
 
    return hwnd; 
} 
 
static bool same_top_level(HWND a, HWND b) 
{ 
    return normalize_top_level(a) == normalize_top_level(b); 
} 
 
static bool is_foreground(HWND hwnd) 
{ 
    HWND fg = GetForegroundWindow(); 
    return fg && same_top_level(fg, hwnd); 
} 
 
static void tap_alt_key(void) 
{ 
    INPUT inputs[2] = {0}; 
 
    inputs[0].type = INPUT_KEYBOARD; 
    inputs[0].ki.wVk = VK_MENU; 
 
    inputs[1].type = INPUT_KEYBOARD; 
    inputs[1].ki.wVk = VK_MENU; 
    inputs[1].ki.dwFlags = KEYEVENTF_KEYUP; 
 
    SendInput(2, inputs, sizeof(INPUT)); 
} 
 
bool activate_window(HWND hwnd) 
{ 
    DWORD current_tid; 
    DWORD foreground_tid = 0; 
    DWORD target_tid = 0; 
    DWORD dummy_pid = 0; 
    HWND foreground_hwnd; 
    BOOL attached_foreground = FALSE; 
    BOOL attached_target = FALSE; 
    MSG msg; 
 
    hwnd = normalize_top_level(hwnd); 
 
    if (!hwnd) return false; 
    if (!IsWindow(hwnd)) return false; 
    if (!IsWindowVisible(hwnd)) return false; 
 
    if (is_foreground(hwnd)) return true; 
 
    /* 
        AttachThreadInput 用に自スレッドの message queue を作る。 
    */ 
    PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); 
 
    current_tid = GetCurrentThreadId(); 
 
    foreground_hwnd = GetForegroundWindow(); 
    if (foreground_hwnd && IsWindow(foreground_hwnd)) { 
        foreground_tid = GetWindowThreadProcessId(foreground_hwnd, &dummy_pid); 
    } 
 
    target_tid = GetWindowThreadProcessId(hwnd, &dummy_pid); 
 
    if (foreground_tid && foreground_tid != current_tid) { 
        attached_foreground = AttachThreadInput(current_tid, foreground_tid, TRUE); 
    } 
 
    if (target_tid && target_tid != current_tid && target_tid != foreground_tid) { 
        attached_target = AttachThreadInput(current_tid, target_tid, TRUE); 
    } 
 
    if (IsIconic(hwnd)) { 
        ShowWindowAsync(hwnd, SW_RESTORE); 
    } else { 
        ShowWindowAsync(hwnd, SW_SHOW); 
    } 
 
    SetWindowPos( 
        hwnd, 
        HWND_TOP, 
        0, 
        0, 
        0, 
        0, 
        SWP_NOMOVE | 
        SWP_NOSIZE | 
        SWP_SHOWWINDOW | 
        SWP_ASYNCWINDOWPOS 
    ); 
 
    BringWindowToTop(hwnd); 
    SetForegroundWindow(hwnd); 
    SetActiveWindow(hwnd); 
    SetFocus(hwnd); 
 
    if (attached_target) { 
        AttachThreadInput(current_tid, target_tid, FALSE); 
    } 
 
    if (attached_foreground) { 
        AttachThreadInput(current_tid, foreground_tid, FALSE); 
    } 
 
    if (is_foreground(hwnd)) { 
        return true; 
    } 
 
    /* 
        UWP / ApplicationFrameHost が foreground の場合などの fallback。 
        ALT 入力後に SetForegroundWindow が通る場合がある。 
    */ 
    tap_alt_key(); 
 
    if (IsIconic(hwnd)) { 
        ShowWindowAsync(hwnd, SW_RESTORE); 
    } else { 
        ShowWindowAsync(hwnd, SW_SHOW); 
    } 
 
    SetWindowPos( 
        hwnd, 
        HWND_TOP, 
        0, 
        0, 
        0, 
        0, 
        SWP_NOMOVE | 
        SWP_NOSIZE | 
        SWP_SHOWWINDOW | 
        SWP_ASYNCWINDOWPOS 
    ); 
 
    BringWindowToTop(hwnd); 
    SetForegroundWindow(hwnd); 
 
    return is_foreground(hwnd); 
}

この記事を編集
この記事を共有:

コメント


前の記事
ffplay を軽量なプレゼンテーションプレビューウィンドウとして使う
次の記事
systemd user service のログを journalctl で確認する