C# 外部プログラムを起動させた際に、特定のフォームよりも前面に表示する方法

投稿者: Anonymous

お世話になります。

C#にて、外部プログラム(calcやnotepadなど、exeで単体起動するもの)を呼び出すとき
(現在はProcess.Startを使用していますが、他の方法でも可)、特定のフォームより
必ず前面になるように表示されるようにする方法はありますでしょうか。

自分で作ったフォームなら、子フォーム.Ownerや、親フォーム.AddOwnedFormで実現できますが、
これを外部アプリに適用する方法です。

何卒よろしくお願いいたします。

解決

Win32APIのSetWindowLongPtrを使用します。

// 実際の処理
private void Form_Click(object sender, EventArgs e)
{
    var p = Process.Start("notepad");

    SetWindowLongPtr(new HandleRef(p, p.MainWindowHandle), GWL_HWNDPARENT, Handle); // ※HandleはFormのプロパティ
}

// P/Invokeの定義 (pinvoke.net参照)
public static IntPtr SetWindowLongPtr(HandleRef hWnd, int nIndex, IntPtr dwNewLong)
{
    if (IntPtr.Size == 8)
        return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
    else
        return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
}

[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong);

[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong);

private const int GWL_HWNDPARENT = -8;

なお相手ウィンドウのハンドルはこの方法では取れない場合がありますのでSpy++等で確認してください。

追記 (失敗した場合の対処)

ご指摘の通り上記の処理は他プロセスも絡むため必ず成功するわけではありません。
なので処理結果の判定とリトライが必要です。

一般論として検証方法は「引数の事前チェック」と「戻り値の判定」ですが、今回の場合成功した場合に返される「変更前の値」が0でエラー時の戻り値と区別できないため、別のAPIで設定値が反映されているか確認するのが良いでしょう。

// 設定値取得用のSetWindowLongPtrの定義

private static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
{
    if (IntPtr.Size == 8)
        return GetWindowLongPtr64(hWnd, nIndex);
    else
        return new IntPtr(GetWindowLong32(hWnd.ToInt32(), nIndex));
}

[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
private static extern int GetWindowLong32(int hWnd, int nIndex);

[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);

// 起動およびオーナー設定

var p = Process.Start("notepad");

for (; ; )
    // TODO:無限ループにならないよう適当に上限を決める
{
    if (p.MainWindowHandle != IntPtr.Zero)
        // ハンドルが得られていない場合はSetWindowLongPtrを実行せず待機
    {
        SetWindowLongPtr(new HandleRef(p, p.MainWindowHandle), GWL_HWNDPARENT, Handle);

        // GWL_HWNDPARENTを再取得してNULLでないことを確認
        var owner = GetWindowLongPtr(p.MainWindowHandle, GWL_HWNDPARENT);
        if (owner != IntPtr.Zero)
        {
            break;
        }
    }
    Thread.Sleep(1);
}
回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *