-->

Specify monitor when opening file. (.bat)

2020-01-29 02:38发布

问题:

The following .bat file below simply opens two text files overlaying them, but I'm wondering if it's possible to define a specific display source, or if anyone can assist in providing the correct parameters.

@echo off

START /max /wait NOTEPAD.EXE C:\test\screen1.txt

START /max /wait NOTEPAD.EXE C:\test\screen2.txt

What I'm trying to get:

@echo off

START /max /wait NOTEPAD.EXE C:\test\screen1.txt "monitor1"  
START /max /wait NOTEPAD.EXE C:\test\screen2.txt "monitor2"

So the results I am trying to receive is that screen1.txt opens on monitor1, and screen2.txt on monitor2.

回答1:

Unless the application you're launching has a command-line switch for it, there's no easy way to specify on which monitor to display a window. As far as I'm aware, neither start nor notepad supports such a switch. The closest solution I've found is to move a window after it's already open.

Edit: user32.dll SetWindowPos() invoked from PowerShell

Here's a hybrid batch + PowerShell script to launch a program and move it to a specific monitor. Save it with a .bat extension.

<# : batch portion
@echo off & setlocal disabledelayedexpansion

set args=%*
call set args=%%args:%1 %2=%%
set "exe=%~2"
set "monitor=%~1"
set "scriptname=%~nx0"
powershell -noprofile "iex (${%~f0} | out-string)"
exit /b %ERRORLEVEL%

: end batch / begin powershell #>

function usage() {
    write-host -nonewline "Usage: "
    write-host -f white "$env:scriptname monitor# filename [arguments]`n"
    write-host -nonewline "* "
    write-host -f white -nonewline "monitor# "
    write-host "is a 1-indexed integer.  Monitor 1 = 1, monitor 2 = 2, etc."
    write-host -nonewline "* "
    write-host -f white -nonewline "filename "
    write-host "is an executable or a document or media file.`n"
    write-host -nonewline "$env:scriptname mimics "
    write-host -f white -nonewline "start"
    write-host ", searching for filename both in %PATH% and"
    write-host "in Windows' app paths (web browsers, media players, etc).`n"
    write-host "Examples:"
    write-host "To display YouTube in Firefox on your second monitor, do"
    write-host -f white "     $env:scriptname 2 firefox `"www.youtube.com`"`n"
    write-host "To play an mp3 file using the default player on monitor 1:"
    write-host -f white "     $env:scriptname 1 mp3file.mp3"
    exit 1
}

add-type user32_dll @'
    [DllImport("user32.dll")]
    public static extern void SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
        int x, int y, int cx, int cy, uint uFlags);
'@ -namespace System

add-type -as System.Windows.Forms
if ($env:monitor -gt [windows.forms.systeminformation]::MonitorCount) {
    [int]$monitor = [windows.forms.systeminformation]::MonitorCount
} else {
    [int]$monitor = $env:monitor
}
try {
    if ($env:args) {
        $p = start $env:exe $env:args -passthru
    } else {
        $p = start $env:exe -passthru
    }
}
catch { usage }

$shell = new-object -COM Wscript.Shell
while (-not $shell.AppActivate($p.Id) -and ++$i -lt 100) { sleep -m 50 }

try {
    $x = [Windows.Forms.Screen]::AllScreens[--$monitor].Bounds.X
    $hwnd = (Get-Process -id $p.Id)[0].MainWindowHandle
    [user32_dll]::SetWindowPos($hwnd, [intptr]::Zero, $x, 0, 0, 0, 0x41);
}
finally { exit 0 }

Original answer: compile and link c# executable

And moving a window is no easy task, either. See this post for some other options. But here's a batch script that will compose and link a C# app on the fly to handle window moves.

@echo off
setlocal

:: // generate c.cs
call :heredoc movewind >"%temp%\c.cs" && goto compile_and_link
// syntax: movewind.exe [pid | "window title"] x y
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class movewind {
    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    static void Main(string[] args) {
        int pid;
        string title;
        bool res = Int32.TryParse(args[0], out pid);
        if (res) {title = Process.GetProcessById(pid).MainWindowTitle;} else {title = args[0];}
        IntPtr handle = FindWindow(null, title);
        try {
            SetWindowPos(handle, IntPtr.Zero, Convert.ToInt32(args[1]), Convert.ToInt32(args[2]), 0, 0, 0x41);
        }
        catch (Exception e) {
            Console.WriteLine("Exception caught while attempting to move window with handle " + handle);
            Console.WriteLine(e);
        }
    }
}
:compile_and_link

set "movewind=%temp%\movewind.exe"

for /f "delims=" %%I in ('dir /b /s "%windir%\microsoft.net\*csc.exe"') do (
    if not exist "%movewind%" "%%I" /nologo /out:"%movewind%" "%temp%\c.cs" 2>NUL
)
del "%temp%\c.cs"
if not exist "%movewind%" (
    echo Error: Please install .NET 2.0 or newer.
    goto :EOF
)

:: // get left monitor width
for /f "tokens=2 delims==" %%I in ('wmic desktopmonitor get screenwidth /format:list') do set "x=%%I"

:: // make sure test environment is in place
if not exist "c:\test" mkdir "c:\test"
if not exist "c:\test\screen1.txt" >"c:\test\screen1.txt" echo This should be on the left.
if not exist "c:\test\screen2.txt" >"c:\test\screen2.txt" echo This should be on the right.

:: // syntax: movewind.exe [pid | "window title"] x y
start /max notepad.exe "c:\test\screen1.txt"
call :movewind "screen1.txt - Notepad" 0 0
start /max notepad.exe "c:\test\screen2.txt"
call :movewind "screen2.txt - Notepad" %x% 0

del "%movewind%"

:: // end main runtime
goto :EOF

:: // SCRIPT FUNCTIONS

:movewind <title> <x> <y>
tasklist /v | find /i "%~1" && (
    "%movewind%" "%~1" %~2 %~3
    goto :EOF
) || (
    ping -n 1 -w 500 169.254.1.1 >NUL
    goto movewind
)

:heredoc <uniqueIDX>
:: // https://stackoverflow.com/a/15032476/1683264
setlocal enabledelayedexpansion
set go=
for /f "delims=" %%A in ('findstr /n "^" "%~f0"') do (
    set "line=%%A" && set "line=!line:*:=!"
    if defined go (if #!line:~1!==#!go::=! (goto :EOF) else echo(!line!)
    if "!line:~0,13!"=="call :heredoc" (
        for /f "tokens=3 delims=>^ " %%i in ("!line!") do (
            if #%%i==#%1 (
                for /f "tokens=2 delims=&" %%I in ("!line!") do (
                    for /f "tokens=2" %%x in ("%%I") do set "go=%%x"
                )
            )
        )
    )
)
goto :EOF


回答2:

(Win 7) To specify location to open a CMD window just run the batch or CMD and then position the CMD window where you want it, including on which monitor if you have more than one, and then right click on its title bar, select Properties, and click OK. The next time the CMD window opens using the same batch or icon it will be in the new location.