💬

C#で外部アプリを起動した後すぐ元のウインドウにフォーカスを戻す方法

2022/10/23に公開

どんな場合に必要なコードか

MusicBeeという自動プレイリスト(高評価を付けたのに半年以上再生してない曲やミュージックビデオを抽出したりできる)などの機能が付いた音楽プレイヤーがあり、以下のようなミュージックビデオなどの動画も音楽と同じように連続再生できるプラグインを公開しています。

https://github.com/lamrongol/MusicBeeVlcVideoPlayPlugin

しかし、このプラグインではVLCメディアプレイヤーで動画を再生してるのですが、VLCを起動するとそちらにフォーカスが移ってしまいます。そのため例えば電子書籍をキーボードの「←」「→」キーで読んでるときに動画が再生されるとVLCにフォーカスが移ってキー操作がそちらに反映されてしまいます。
非アクティブ状態で起動できれば一番良いのですが、それはできないようなのでフォーカスをすぐに元に戻すコードを書きました。

実際のコード

using System.Diagnostics;
using System.Threading.Tasks;
//中略
//具体例
        LaunchExternalApp(string fileUrl)
        {
            IntPtr currentWindow = GetForegroundWindow();

            vlcProcess = new System.Diagnostics.Process();
            vlcProcess.StartInfo.FileName = vlcPath;
            vlcProcess.StartInfo.Arguments = fileUrl;
            vlcProcess.Start();
            vlcProcess.WaitForInputIdle();

            string strTitleContains = Path.GetFileName(fileUrl);
            ReactivateCurrentWindow(vlcProcess, strTitleContains, currentWindow);
        }

//以下、汎用的に使えるコード
        [DllImport("user32.dll")]
        static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        static async void ReactivateCurrentWindow(Process newProcess, string strTitleContains, IntPtr currentWindow)
        {
            for(int i = 0; i<100; i++)
            {
                newProcess.Refresh();//これをやらないとProcess情報が更新されない
                if (!newProcess.MainWindowHandle.Equals(IntPtr.Zero))
                {
                    if (newProcess.MainWindowTitle.Contains(strTitleContains))
                    {
                        SetForegroundWindow(currentWindow);
                        return;
                    }
                }
                await Task.Delay(10);//10ミリ秒待つ
            }
        }

GetForegroundWindow()は現在のアクティブウインドウを取得するメソッド、SetForegroundWindow()は指定したプロセスをアクティブにするメソッドで、本来なら「現在のアクティブウインドウを取得→外部アプリを起動→元のウインドウをアクティブ化」でいいはずですが、実際にはプロセスをStart()させた後外部アプリの準備が整ってフォーカスが移るまで少し時間がかかるのでそれまで待たなければいけません。
ProcessクラスにはWaitForInputIdle()というプロセスの準備が整うまで待つメソッドがあるはずなのですが、実際にはこれで待った後もまだプロセスの準備が整ってません。
上のコードのReactivateCurrentWindow()メソッドではまずnewProcess(今回の場合はVLC)のMainWindowHandleを取得してこれがゼロでないことを確認しています。次にMainWindowTitleを取得しそれに含まれてるはずの文字列が入っていたら完全に起動したと判断し、外部アプリをStart()する前にGetForegroundWindow()で取得しておいたアクティブウインドウを再度アクティブ化しています。
VLCの場合再生しているファイル名がウインドウタイトルに含まれるため、上のコードではファイル名がウインドウタイトルに含まれていたら完全に起動したと判断しています。

Discussion