マイナビニュースマイナビ

ウインドウを配置するスクリプト - 外部ディスプレイ対応編(その2)

【連載】

PowerShell Core入門 - 基本コマンドの使い方

【第170回】ウインドウを配置するスクリプト - 外部ディスプレイ対応編(その2)

[2021/10/15 08:00]後藤大地 ブックマーク ブックマーク

オフィス勤務とテレワークが混在するハイブリッドワークでは、働く場所に応じて異なるディスプレイを利用するというケースが多い。その場合、ピクセルという絶対数での配置指定ではなく、スクリーンサイズに対する比率で指定できるようにしておくと、どのディスプレイでも同じような配置を実現できる。

そこで前回、座標(X、Y、幅、高さ)の指定にスクリーンサイズに対する比率(0~1の実数)を指定できるようにした。こうすることで、利用するスクリーンのサイズが変わっても同じ割合でウインドウを配置することができるようになった。

本連載で作成してきたスクリプトでは、ピクセル指定でマイナス値を指定した場合には右下からの距離と見なして動作するようにしてきた。当然、割合指定の場合も同じようにマイナス値での指定ができたほうがよい。

ということで、今回は比率での座標指定をマイナス値でも行えるように前回のスクリプトを書き換える。完成形となる「window_deploy.ps1」は次の通りだ。

#!/usr/bin/env pwsh

#====================================================================
# ウインドウ配置スクリプト
#====================================================================

#====================================================================
# 引数を処理
#====================================================================
Param(
    [String]$ProcessName="*",   # プロセス名
    [String]$WindowTitle=".*",  # ウインドウタイトル(正規表現)
    [Int32]$X="0",          # ウインドウ左上X座標。負値は右下逆X座標
    [Int32]$Y="0",          # ウインドウ左上Y座標。負値は右下逆Y座標
    [Int32]$Width="-1",     # ウインドウ幅
    [Int32]$Height="-1",        # ウインドウ高
    [Double]$XRatio="0",        # ウインドウ左上X座標。負値は右下逆X座標
                    # (スクリーン幅を1とし、0~1の実数で指定)
    [Double]$YRatio="0",        # ウインドウ左上Y座標。負値は右下逆Y座標
                    # (スクリーン高を1とし、0~1の実数で指定)
    [Double]$WidthRatio="-1",   # ウインドウ幅(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$HeightRatio="-1",  # ウインドウ高(スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
)

#====================================================================
# ウインドウプロセスを一覧表示
#====================================================================
if ($WindowProcessList) {
    Get-Process                         | 
    ? {$_.MainWindowHandle -ne 0 }              | 
    Format-Table -Property Id,ProcessName,MainWindowTitle
    exit
}

#====================================================================
# Win32 APIのインポート
#====================================================================
Add-Type @"
using System;
using System.Runtime.InteropServices;

public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

public class WinAPI
{
    // ウインドウの現在の座標データを取得する関数
    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    // ウインドウの座標を変更する関数
    [DllImport("user32.dll")]
    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); 

    // スクリーンサイズを取得する関数
    [DllImport("user32.dll")]
    public static extern int GetSystemMetrics(int nIndex); 
}
"@

#====================================================================
# ウインドウを配置する関数 Deploy-Window
#====================================================================
function Deploy-Window {
    param (
        $wh  # ウインドウハンドラ
    )

    # スクリーン幅を取得
    $screenWidth = [WinAPI]::GetSystemMetrics(0);

    # スクリーン高さを取得
    $screenHeight = [WinAPI]::GetSystemMetrics(1);

    # ウインドウ座標データ構造体
    $rc = New-Object RECT

    # ウインドウの現在の座標データを取得
    [WinAPI]::GetWindowRect($wh, [ref]$rc) > $null

    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
        $X = $screenWidth + $X - $Width
    }
    if ($Y -lt 0) {
        $Y = $screenHeight + $Y - $Height
    }

    # 割合指定をXおよびYの正値へ変換
    if ($XRatio -ge 0) {
        $X = $screenWidth * $XRatio
    }
    else {
        $X = $screenWidth + ($screenWidth * $XRatio) - $Width
    }
    if ($YRatio -ge 0) {
        $Y = $screenHeight * $YRatio 
    }
    else {
        $Y = $screenHeight + ($screenHeight * $YRatio) - $Height
    }

    # 割合指定をWidthの値へ変換
    if ($WidthRatio -ge 0) {
        $Width = $screenWidth * $WidthRatio
    }
    # 幅指定がない場合、現在の幅を設定
    elseif ($Width -eq -1) {
        $Width = $rc.Right - $rc.Left;
    }

    # 割合指定をHeightの値へ変換
    if ($HeightRatio -ge 0) {
        $Height = $screenHeight * $HeightRatio
    }
    # 高指定がない場合、現在の高を設定
    elseif ($Height -eq -1) {
        $Height = $rc.Bottom - $rc.Top;
    }

    # ウインドウを指定された座標に指定されたサイズで配置する
    [WinAPI]::MoveWindow($wh, $X, $Y, $Width, $Height, $true) > $null
}

#====================================================================
# 対象となるウインドウを選択し、サイズを変更
#====================================================================
Get-Process -Name $processName |
    ? { $_.MainWindowHandle -ne 0 } |
    ? { $_.MainWindowTitle -match "$windowTitle" } |
    % {
        # ウインドウを配置
        Deploy-Window($_.MainWindowHandle);
}

また、第164~166回でウインドウを移動させるだけのスクリプトも作成したが、こちらも同様に比率指定でマイナス値が使えるように書き換える(window_move.ps1)。

#!/usr/bin/env pwsh

#====================================================================
# ウインドウ移動スクリプト
#====================================================================

#====================================================================
# 引数を処理
#====================================================================
Param(
    [String]$ProcessName="*",   # プロセス名
    [String]$WindowTitle=".*",  # ウインドウタイトル(正規表現)
    [Int32]$X="0",          # ウインドウ左上X座標。負値は右下逆X座標
    [Int32]$Y="0",          # ウインドウ左上Y座標。負値は右下逆Y座標
    [Double]$XRatio="0",        # ウインドウ左上X座標。負値は右下逆X座標
                    # (スクリーン幅を1とし、0~1の実数で指定)
    [Double]$YRatio="0",        # ウインドウ左上Y座標。負値は右下逆Y座標
                    # (スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
)

#====================================================================
# ウインドウプロセスを一覧表示
#====================================================================
if ($WindowProcessList) {
    Get-Process                         | 
    ? {$_.MainWindowHandle -ne 0 }              | 
    Format-Table -Property Id,ProcessName,MainWindowTitle
    exit
}

#====================================================================
# Win32 APIのインポート
#====================================================================
Add-Type @"
using System;
using System.Runtime.InteropServices;

public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

public class WinAPI
{
    // ウインドウの現在の座標データを取得する関数
    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    // ウインドウの座標を変更する関数
    [DllImport("user32.dll")]
    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); 

    // スクリーンサイズを取得する関数
    [DllImport("user32.dll")]
    public static extern int GetSystemMetrics(int nIndex); 
}
"@

#====================================================================
# ウインドウを移動する関数 Move-Window
#====================================================================
function Move-Window {
    param (
        $wh  # ウインドウハンドラ
    )

    # スクリーン幅を取得
    $screenWidth = [WinAPI]::GetSystemMetrics(0);

    # スクリーン高さを取得
    $screenHeight = [WinAPI]::GetSystemMetrics(1);

    # ウインドウ座標データ構造体
    $rc = New-Object RECT

    # ウインドウの現在の座標データを取得
    [WinAPI]::GetWindowRect($wh, [ref]$rc) > $null

    # 取得した座標データからウインドウの幅と高さを計算
    $Width = $rc.Right - $rc.Left;
    $Height = $rc.Bottom - $rc.Top;

    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
        $X = $screenWidth + $X - $Width
    }
    if ($Y -lt 0) {
        $Y = $screenHeight + $Y - $Height
    }

    # 割合指定をXおよびYの正値へ変換
    if ($XRatio -ge 0) {
        $X = $screenWidth * $XRatio
    }
    else {
        $X = $screenWidth + ($screenWidth * $XRatio) - $Width
    }
    if ($YRatio -ge 0) {
        $Y = $screenHeight * $YRatio 
    }
    else {
        $Y = $screenHeight + ($screenHeight * $YRatio) - $Height
    }

    # ウインドウのサイズはそのままに、左上の場所を変更
    [WinAPI]::MoveWindow($wh, $X, $Y, $Width, $Height, $true) > $null
}

#====================================================================
# 対象となるウインドウを選択し、サイズを変更
#====================================================================
Get-Process -Name $processName |
    ? { $_.MainWindowHandle -ne 0 } |
    ? { $_.MainWindowTitle -match "$windowTitle" } |
    % {
        # ウインドウを移動
        Move-Window($_.MainWindowHandle);
}

それぞれ前回からの変更分は次のようになる。

◆/window_deploy.ps1の変更内容

--- ".\\window_deploy.ps1.old"  2021-09-30 13:26:05.160958800 +0900
+++ ".\\window_deploy.ps1"  2021-10-11 18:25:20.824827200 +0900
@@ -10,12 +10,14 @@
 Param(
    [String]$ProcessName="*",   # プロセス名
    [String]$WindowTitle=".*",  # ウインドウタイトル(正規表現)
-   [Int32]$X="0",          # ウインドウ左上X座標
-   [Int32]$Y="0",          # ウインドウ左上Y座標
-   [Int32]$Width="0",      # ウインドウ幅
-   [Int32]$Height="0",     # ウインドウ高
-   [Double]$XRatio="-1",       # ウインドウ左上X座標(スクリーン幅を1とし、0~1の実数で指定)
-   [Double]$YRatio="-1",       # ウインドウ左上Y座標(スクリーン高を1とし、0~1の実数で指定)
+   [Int32]$X="0",          # ウインドウ左上X座標。負値は右下逆X座標
+   [Int32]$Y="0",          # ウインドウ左上Y座標。負値は右下逆Y座標
+   [Int32]$Width="-1",     # ウインドウ幅
+   [Int32]$Height="-1",        # ウインドウ高
+   [Double]$XRatio="0",        # ウインドウ左上X座標。負値は右下逆X座標
+                   # (スクリーン幅を1とし、0~1の実数で指定)
+   [Double]$YRatio="0",        # ウインドウ左上Y座標。負値は右下逆Y座標
+                   # (スクリーン高を1とし、0~1の実数で指定)
    [Double]$WidthRatio="-1",   # ウインドウ幅(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$HeightRatio="-1",  # ウインドウ高(スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
@@ -70,6 +72,12 @@
        $wh  # ウインドウハンドラ
    )

+   # スクリーン幅を取得
+   $screenWidth = [WinAPI]::GetSystemMetrics(0);
+
+   # スクリーン高さを取得
+   $screenHeight = [WinAPI]::GetSystemMetrics(1);
+
    # ウインドウ座標データ構造体
    $rc = New-Object RECT

@@ -78,53 +86,41 @@

    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
-       # スクリーン幅を取得
-       $screenWidth = [WinAPI]::GetSystemMetrics(0);
-       # Xの値を算出
        $X = $screenWidth + $X - $Width
    }
    if ($Y -lt 0) {
-       # スクリーン高さを取得
-       $screenHeight = [WinAPI]::GetSystemMetrics(1);
-       # Yの値を算出
        $Y = $screenHeight + $Y - $Height
    }

-   # 座標が0~1の割合で指定されている場合にXおよびYの正値へ変換
+   # 割合指定をXおよびYの正値へ変換
    if ($XRatio -ge 0) {
-       # スクリーン幅を取得
-       $screenWidth = [WinAPI]::GetSystemMetrics(0);
-       # Xの値を算出
        $X = $screenWidth * $XRatio
    }
+   else {
+       $X = $screenWidth + ($screenWidth * $XRatio) - $Width
+   }
    if ($YRatio -ge 0) {
-       # スクリーン高さを取得
-       $screenHeight = [WinAPI]::GetSystemMetrics(1);
-       # Yの値を算出
        $Y = $screenHeight * $YRatio 
    }
+   else {
+       $Y = $screenHeight + ($screenHeight * $YRatio) - $Height
+   }

-   # 幅が0~1の割合で指定されている場合にWidthの値へ変換
+   # 割合指定をWidthの値へ変換
    if ($WidthRatio -ge 0) {
-       # スクリーン幅を取得
-       $screenWidth = [WinAPI]::GetSystemMetrics(0);
-       # Widthの値を算出
        $Width = $screenWidth * $WidthRatio
    }
-   # 幅指定がないため、取得した座標データからウインドウの現在の幅を設定
-   elseif ($Width -eq 0) {
+   # 幅指定がない場合、現在の幅を設定
+   elseif ($Width -eq -1) {
        $Width = $rc.Right - $rc.Left;
    }

-   # 高が0~1の割合で指定されている場合にHeightの値へ変換
+   # 割合指定をHeightの値へ変換
    if ($HeightRatio -ge 0) {
-       # スクリーン高を取得
-       $screenHeight = [WinAPI]::GetSystemMetrics(1);
-       # Heightの値を算出
        $Height = $screenHeight * $HeightRatio
    }
-   # 高指定がないため、取得した座標データからウインドウの現在の高を設定
-   elseif ($Height -eq 0) {
+   # 高指定がない場合、現在の高を設定
+   elseif ($Height -eq -1) {
        $Height = $rc.Bottom - $rc.Top;
    }

◆window_move.ps1の変更内容

--- ".\\window_move.ps1.old"    2021-09-30 13:26:10.536241400 +0900
+++ ".\\window_move.ps1"    2021-10-11 18:23:30.704400500 +0900
@@ -10,10 +10,12 @@
 Param(
    [String]$ProcessName="*",   # プロセス名
    [String]$WindowTitle=".*",  # ウインドウタイトル(正規表現)
-   [Int32]$X="0",          # ウインドウ左上X座標
-   [Int32]$Y="0",          # ウインドウ左上Y座標
-   [Double]$XRatio="-1",       # ウインドウ左上X座標(スクリーン幅を1とし、0~1の実数で指定)
-   [Double]$YRatio="-1",       # ウインドウ左上Y座標(スクリーン高を1とし、0~1の実数で指定)
+   [Int32]$X="0",          # ウインドウ左上X座標。負値は右下逆X座標
+   [Int32]$Y="0",          # ウインドウ左上Y座標。負値は右下逆Y座標
+   [Double]$XRatio="0",        # ウインドウ左上X座標。負値は右下逆X座標
+                   # (スクリーン幅を1とし、0~1の実数で指定)
+   [Double]$YRatio="0",        # ウインドウ左上Y座標。負値は右下逆Y座標
+                   # (スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
 )

@@ -66,6 +68,12 @@
        $wh  # ウインドウハンドラ
    )

+   # スクリーン幅を取得
+   $screenWidth = [WinAPI]::GetSystemMetrics(0);
+
+   # スクリーン高さを取得
+   $screenHeight = [WinAPI]::GetSystemMetrics(1);
+
    # ウインドウ座標データ構造体
    $rc = New-Object RECT

@@ -73,39 +81,33 @@
    [WinAPI]::GetWindowRect($wh, [ref]$rc) > $null

    # 取得した座標データからウインドウの幅と高さを計算
-   $width = $rc.Right - $rc.Left;
-   $height = $rc.Bottom - $rc.Top;
+   $Width = $rc.Right - $rc.Left;
+   $Height = $rc.Bottom - $rc.Top;

    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
-       # スクリーン幅を取得
-       $screenWidth = [WinAPI]::GetSystemMetrics(0);
-       # Xの値を算出
-       $X = $screenWidth + $X - $width
+       $X = $screenWidth + $X - $Width
    }
    if ($Y -lt 0) {
-       # スクリーン高さを取得
-       $screenHeight = [WinAPI]::GetSystemMetrics(1);
-       # Yの値を算出
-       $Y = $screenHeight + $Y - $height
+       $Y = $screenHeight + $Y - $Height
    }

-   # 座標が0~1の割合で指定されている場合にXおよびYの正値へ変換
+   # 割合指定をXおよびYの正値へ変換
    if ($XRatio -ge 0) {
-       # スクリーン幅を取得
-       $screenWidth = [WinAPI]::GetSystemMetrics(0);
-       # Xの値を算出
        $X = $screenWidth * $XRatio
    }
+   else {
+       $X = $screenWidth + ($screenWidth * $XRatio) - $Width
+   }
    if ($YRatio -ge 0) {
-       # スクリーン高さを取得
-       $screenHeight = [WinAPI]::GetSystemMetrics(1);
-       # Yの値を算出
        $Y = $screenHeight * $YRatio 
    }
+   else {
+       $Y = $screenHeight + ($screenHeight * $YRatio) - $Height
+   }

    # ウインドウのサイズはそのままに、左上の場所を変更
-   [WinAPI]::MoveWindow($wh, $X, $Y, $width, $height, $true) > $null
+   [WinAPI]::MoveWindow($wh, $X, $Y, $Width, $Height, $true) > $null
 }

 #====================================================================

次に書き換えた内容を見ていこう。

書き換えた内容を確認

これまでのスクリプトは、条件分岐した後で必要に応じてスクリーンサイズを取得する処理にしていた。ピクセル指定で正の整数が使われているだけの場合、スクリーンサイズは使われないので、無駄な処理が行われないようにするためだ。

しかし、分岐処理が増えて読みにくくなってきた。スクリーンサイズを取得する処理が多すぎるのだ。このため、スクリーンサイズを取得する処理を抜き出して先に処理するように変更した。

◆スクリーンサイズを取得する処理

    # スクリーン幅を取得
    $screenWidth = [WinAPI]::GetSystemMetrics(0);

    # スクリーン高さを取得
    $screenHeight = [WinAPI]::GetSystemMetrics(1);

これに合わせてほかの部分も書き換えている。

◆スクリーンサイズを取得する処理を抜き出したので書き換え

    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
        $X = $screenWidth + $X - $Width
    }
    if ($Y -lt 0) {
        $Y = $screenHeight + $Y - $Height
    }

◆スクリーンサイズを取得する処理を抜き出したので書き換え

    # 割合指定をWidthの値へ変換
    if ($WidthRatio -ge 0) {
        $Width = $screenWidth * $WidthRatio
    }
    # 幅指定がない場合、現在の幅を設定
    elseif ($Width -eq -1) {
        $Width = $rc.Right - $rc.Left;
    }

    # 割合指定をHeightの値へ変換
    if ($HeightRatio -ge 0) {
        $Height = $screenHeight * $HeightRatio
    }
    # 高指定がない場合、現在の高を設定
    elseif ($Height -eq -1) {
        $Height = $rc.Bottom - $rc.Top;
    }

そして今回の部分の機能追加だ。前回までのスクリプトでは、比率指定のXとYは、正の値のときだけ処理している。この部分に負の値だった場合の処理を追加する。

    # 割合指定をXおよびYの正値へ変換
    if ($XRatio -ge 0) {
        $X = $screenWidth * $XRatio
    }
    else {
        $X = $screenWidth + ($screenWidth * $XRatio) - $Width
    }
    if ($YRatio -ge 0) {
        $Y = $screenHeight * $YRatio 
    }
    else {
        $Y = $screeneight + ($screenHeight * $YRatio) - $Height
    }H

これで比率指定においても、ピクセル指定と同じようにマイナス値が使えるようになった。

window_move.ps1で行った書き換えも基本的に上記と同じ内容だ。

比率で指定する場合、ピクセルで指定するケースに比べるとマイナス値で右下からの距離として指定する必要性は低い。しかし、実際に使っているとピクセル指定のときと同じ要領で指定したくなってくるものだ。このように、欲しくなったらさっと機能を追加できるのがスクリプトの良いところである。

使ってみよう

では、作成したスクリプトを実行してみよう。シンプルな指定で済むので、window_move.ps1のほうで動きを確認する。まず、前回の書き換え内容である正の値での比率指定を実行する。

window_move "*" "新着記事.*" 0 0 0.1 0.1

実行結果は次の通りだ。

実行結果

次に、負の値での比率指定を試してみよう。

window_move "*" "新着記事.*" 0 0 -0.1 -0.1

実行すると次のようになる。右下からの指定として機能していることがおわかりいただけるだろう。

実行結果

スクリーンサイズがあまりに違うと(例えば、4KのディスプレイとノートPCのディスプレイといった具合に)比率指定でスクリプトを書いても使い回しは困難なのだが、多少のサイズの違いであれば十分流用できる。

先日公開されたWindows 11ではウインドウの配置を「スナップレイアウト」という機能で手軽に変更することができる(「Windows」+「Z」)。しかし、用意されているレイアウトはそれほど多くはなく、自由にカスタマイズもできない。すでに好みの配置が決まっている場合には、スクリプトを使って自分で指定したほうが使いやすいのだ。

※ 本記事は掲載時点の情報であり、最新のものとは異なる場合がございます。予めご了承ください。

一覧はこちら

連載目次

もっと知りたい!こちらもオススメ

【連載】RPA入門 - ツールで学ぶ活用シーン

【連載】RPA入門 - ツールで学ぶ活用シーン

AIには、ルールベース、機械学習、深層学習(ディープラーニング)の3つのレベルがあり、レベルが上がるに連れてより高度な人工知能を実現しますが、AIのスピンオフという位置付けで、Digital Labor(仮想知的労働者)によるホワイトカラー業務の自動化を実現するRPAが注目されています。

関連リンク

この記事に興味を持ったら"いいね!"を Click
Facebook で TECH+ の人気記事をお届けします
注目の特集/連載
[解説動画] Googleアナリティクス分析&活用講座 - Webサイト改善の正しい考え方
Slackで始める新しいオフィス様式
Google Workspaceをビジネスで活用する
ニューノーマル時代のオウンドメディア戦略
ミッションステートメント
次世代YouTubeクリエイターの成長戦略
IoTでできることを見つけるための発想トレーニング
教えてカナコさん! これならわかるAI入門
AWSではじめる機械学習 ~サービスを知り、実装を学ぶ~
Kubernetes入門
SAFeでつくる「DXに強い組織」~企業の課題を解決する13のアプローチ~
マイクロサービス時代に活きるフレームワーク Spring WebFlux入門
AWSで作るマイクロサービス
マイナビニュース スペシャルセミナー 講演レポート/当日講演資料 まとめ
セキュリティアワード特設ページ

一覧はこちら

今注目のIT用語の意味を事典でチェック!

一覧はこちら

会員登録(無料)

ページの先頭に戻る