【Unity】Unityでウィンドウを最前面にしたりする

# はじめに
Unityで作った画面を強制的に最前面にしたい！という場面がありました。

https://x.com/ayaha__401/status/1817013748240122259

そこで
* ウィンドウを最前面にする
* 右上の最小化、最大化、閉じるボタンを消す

という普段ゲームを作っていたら作らない機能を実装しました。
これを作るためにChatGPTを使用したため、知らないことをアウトプットするためにまとめてみます。

# ソースコード
ウィンドウを最前面にするコードはこちら

https://github.com/ayaha401/WindowFront

ウィンドウのボタンを消すコードはこちら

https://github.com/ayaha401/WindowButtonHide

# ウィンドウを最前面にする
```C#
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();

[DllImport("user32.dll")]
private static extern int SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, uint uFlags);
```

Windowsにある画面を設定するメソッドたちをUnityでも使用できるようにします。
`DllImport`で使用するdllの名前を指定するとそこにある関数を使えるようになります。
普段見かけない`extern`は外部で実装されるメソッドを宣言するために使用するらしいです。

https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/extern

`DllImport`を使う場合は`static`にする必要性があるようです。

## SetForegroundWindow
`SetForegroundWindow`は指定したウィンドウをフォアグラウンドウィンドウに設定します。
フォアグラウンドウィンドウは、ユーザーが現在操作しているウィンドウであり、ユーザーからの入力（キーボードやマウスの操作）を受け取ることができます。

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-setforegroundwindow

## GetActiveWindow
`GetActiveWindow`は現在アクティブなウィンドウのハンドルを取得するために使用します。

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getactivewindow

## SetWindowPos
`SetWindowPos`はウィンドウのサイズ、位置、ウィンドウの前後関係を変更するために使います。

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-setwindowpos

## constたち
```C#
private const uint SWP_NOMOVE = 0x0002;  // 現在位置を保持します
private const uint SWP_NOSIZE = 0x0001;  // 現在のサイズを保持します
private const uint SWP_NOZORDER = 0x0010;  // 現在の Z オーダーを保持します
private const uint SWP_SHOWWINDOW = 0x0040; // ウィンドウが表示されます。
private const int HWND_TOPMOST = -1;  // 最上位以外のすべてのウィンドウの上にウィンドウをPlacesします。
private const int HWND_NOTOPMOST = -2; // 最上位以外のすべてのウィンドウ (つまり、すべての最上位ウィンドウの背後) の上にウィンドウをPlacesします。
```
`SetWindowPos`に入れるための値です。これの最後の引数にはフラグをいくつか組み合わせて設定を渡します。
これらの値が何を表しているのかはドキュメントに書いてあります。

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-setwindowpos

## メソッド
```C#
public void SetWindowOnTop(bool isOnTop)
{
    IntPtr hWnd = GetActiveWindow();
    if (isOnTop)
    {
        SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
    }
    else
    {
        SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
    }

    // アクティブウィンドウに戻す
    SetForegroundWindow(hWnd);
}
```
まず、`GetActiveWindow`を使用してアクティブなウィンドウを取得します。

最前面にするかの部分で`SetWindowPos`に入れる引数が`HWND_TOPMOST`を使うか、代わりに`HWND_NOTOPMOST`を使うかの違いです。この値がウィンドウを最前面にするかの設定を変えます。

最後に設定したウィンドウをユーザーの操作を受け付ける状態にするために`SetForegroundWindow`を使います。

これでウィンドウを最前面にできます！

# ウィンドウのボタンを消す方法
```C#
[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();

[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
```
## GetWindowLong
ウィンドウのスタイル情報を取得します。

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getwindowlongw

## SetWindowLong
ウィンドウのスタイル情報を設定します。

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-setwindowlonga

## const
```C#
private const int GWL_STYLE = -16;  // ウィンドウスタイルを取得 します。
private const int WS_MINIMIZEBOX = 0x00020000;  // 最小化ボタンのスタイル
private const int WS_MAXIMIZEBOX = 0x00010000;  // 最大化ボタンのスタイル
private const int WS_SYSMENU = 0x00080000;  // システムメニュー（閉じるボタン）のスタイル
```

https://learn.microsoft.com/ja-jp/windows/win32/winmsg/window-styles

スタイルのボタンには定数が決まってるらしいのでそれを宣言します。

## メソッド
``` C#
public void SetWindowButtonHide(bool isHide)
{
    IntPtr windowHandle = GetActiveWindow();
    int style = GetWindowLong(windowHandle, GWL_STYLE);

    if (isHide)
    {
        style &= ~WS_MINIMIZEBOX;  // 最小化ボタンを無効化
        style &= ~WS_MAXIMIZEBOX;  // 最大化ボタンを無効化
        style &= ~WS_SYSMENU;      // システムメニュー（閉じるボタン）を無効化
    }
    else
    {
        style |= WS_MINIMIZEBOX;  // 最小化ボタンを有効化
        style |= WS_MAXIMIZEBOX;  // 最大化ボタンを有効化
        style |= WS_SYSMENU;      // システムメニュー（閉じるボタン）を有効化
    }

    SetWindowLong(windowHandle, GWL_STYLE, style);
}
```
まず、`GetActiveWindow`を使用してアクティブなウィンドウを取得します。
次に`GetWindowLong`を使うとウィンドウのスタイルを取得できるので取得します。
ボタンにビット演算で無効化したり、有効化したりします。

**無効化**
`&`はAND演算子です。ビットが両方とも1の場合にだけ1になります。
`~`はNOT演算子です。ビットを反転させます。

つまり、ボタンを無効化するときは
`style`のビット値と反転した`WS_MINIMIZEBOX`をAND演算し、`WS_MINIMIZEBOX`に対応するビットをクリアします。残りのパラメータも同じように処理します。

`style`が`00000000 11111111 00000000 00000000`
`WS_MINIMIZEBOX`が`00000000 00000010 00000000 00000000`

`~WS_MINIMIZEBOX`は反転するので`11111111 11111101 11111111 11111111`
ANDすると`00000000 11111101 00000000 00000000`となります。

**有効化**
`|`はOR演算子です。ビットにどちらかが1なら1になります。
つまり、ボタンを有効化するときは
`style`のビット値と`WS_MINIMIZEBOX`をOR演算し、`WS_MINIMIZEBOX`に対応するビットをセットします。
残りのパラメータも同じように処理します。

一度無効化されてるので`style`は`00000000 11111101 00000000 00000000`です。
`WS_MINIMIZEBOX`は`00000000 00000010 00000000 00000000`なので
ORすると`00000000 11111111 00000000 00000000`になります。

そして決まった設定を`SetWindowLong`でセットしています。

これでウィンドウのボタンを消せます。