Launch an Application and Send It to Second Monitor

Launch an application and send it to second monitor?

Since the window is not yours, you can only move it by invoking the Windows API. You will have to do this:

  • Launch the process.

  • Use FindWindow to retrieve the handle to the window. If the window doesn’t exist yet, the process hasn’t created it yet; sleep for 500ms and then try again. (But don’t go into an infinite loop; stop if you can’t find the window after a reasonable timeout.)

  • Use SetWindowPos to change the position of the window.

If you don’t know the title of the window, you can’t use FindWindow. In that case,

  • Launch the process and get the process handle by retrieving Process.Handle.

  • Use EnumWindows to retrieve all the windows. For each window, use GetWindowThreadProcessId to check whether it belongs to your process. If no window belongs to your process, wait and keep trying.

  • Use SetWindowPos to change the position of the window.

Of course, you can use Screen.AllScreens[n].WorkingArea to retrieve the position and size of the screen you want, and then you can position the window relative to that.

how to launch a second application in a second screen in WPF?

Even though it is a WPF app you can still reference System.Windows.Forms.
Simply right click on the project in solution explorer, add references, and select System.Windows.Forms.

Then use something like this: (you will also need a reference to System.Drawing)

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MaximizeToSecondaryMonitor();
}

public void MaximizeToSecondaryMonitor()
{
var secondaryScreen = Screen.AllScreens.Where(s => !s.Primary).FirstOrDefault();

if (secondaryScreen != null)
{
var workingArea = secondaryScreen.WorkingArea;
this.Left = workingArea.Left;
this.Top = workingArea.Top;
this.Width = workingArea.Width;
this.Height = workingArea.Height;

if (this.IsLoaded)
{
this.WindowState = WindowState.Maximized;
}
}
}
}

code taken from here

How to programmatically start an application on a specific monitor on Windows 10?

As suggested by others, this is intended behavior of Windows and for the good reasons.

Also, you can not rely on default window placement atleast for SumatraPDF, since it surely does not use CW_USEDEFAULT, and instead stores these values in :

%USERPROFILE%\AppData\Roaming\SumatraPDF\SumatraPDF-settings.txt

There are multiple options though:

  1. Use third party tools that monitor top level windows and based on pre-configured rules moves them to specified display. E.g. DisplayFusion, etc.
  2. Use ligher weight solutions like AutoHotkey/AutoIt.
  3. Try and do this in code itself. Following is a working solution. I smoke tested on my box.

Disclaimer: I have not written the entire code, for saving time I pulled it up from couple of sources, tweaked it, glued it together, and tested with SumantraPDF. Do also note, that this code is not of highest standards, but solves your problem, and will act as a can-be-done example.

C++ code: (scroll down for C# code)

#include <Windows.h>
#include <vector>

// 0 based index for preferred monitor
static const int PREFERRED_MONITOR = 1;

struct ProcessWindowsInfo
{
DWORD ProcessID;
std::vector<HWND> Windows;

ProcessWindowsInfo(DWORD const AProcessID)
: ProcessID(AProcessID)
{
}
};

struct MonitorInfo
{
HMONITOR hMonitor;
RECT rect;
};


BOOL WINAPI EnumProcessWindowsProc(HWND hwnd, LPARAM lParam)
{
ProcessWindowsInfo *info = reinterpret_cast<ProcessWindowsInfo*>(lParam);
DWORD WindowProcessID;

GetWindowThreadProcessId(hwnd, &WindowProcessID);

if (WindowProcessID == info->ProcessID)
{
if (GetWindow(hwnd, GW_OWNER) == (HWND)0 && IsWindowVisible(hwnd))
{
info->Windows.push_back(hwnd);
}
}

return true;
}


BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdc, LPRECT lprect, LPARAM lParam)
{
std::vector<MonitorInfo> *info = reinterpret_cast<std::vector<MonitorInfo>*>(lParam);

MonitorInfo monitorInfo = { 0 };

monitorInfo.hMonitor = hMonitor;
monitorInfo.rect = *lprect;

info->push_back(monitorInfo);
return TRUE;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{

// NOTE: for now this code works only when the window is not already visible
// could be easily modified to terminate existing process as required

SHELLEXECUTEINFO info = { 0 };
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_NOCLOSEPROCESS;
info.lpFile = L"C:\\Program Files\\SumatraPDF\\SumatraPDF.exe";
info.nShow = SW_SHOW;

std::vector<MonitorInfo> connectedMonitors;

// Get all available displays
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, reinterpret_cast<LPARAM>(&connectedMonitors));

if (ShellExecuteEx(&info))
{
WaitForInputIdle(info.hProcess, INFINITE);

ProcessWindowsInfo Info(GetProcessId(info.hProcess));

// Go though all windows from that process
EnumWindows((WNDENUMPROC)EnumProcessWindowsProc, reinterpret_cast<LPARAM>(&Info.ProcessID));

if (Info.Windows.size() == 1)
{
// only if we got at most 1 window
// NOTE: applications can have more than 1 top level window. But at least for SumtraPDF this works!

if (connectedMonitors.size() >= PREFERRED_MONITOR)
{
// only move the window if we were able to successfully detect available monitors

SetWindowPos(Info.Windows.at(0), 0, connectedMonitors.at(PREFERRED_MONITOR).rect.left, connectedMonitors.at(PREFERRED_MONITOR).rect.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}

CloseHandle(info.hProcess);
}

return 0;
}

To emphasize one of my comments in code. This code will only work if the process in question is not already running. You can tweak the code as per your requirements otherwise.

Update: Added C# code below, as I realized OP prefers C#. This code also has the termination logic cooked in.

C# code:

[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;

private const int PREFERRED_MONITOR = 1;

static void Main(string[] args)
{

// NOTE: you will have to reference System.Windows.Forms and System.Drawing (or
// equivalent WPF assemblies) for Screen and Rectangle

// Terminate existing SumatraPDF process, else we will not get the MainWindowHandle by following method.
List<Process> existingProcesses = Process.GetProcessesByName("SumatraPDF").ToList();

foreach (var existingProcess in existingProcesses)
{
// Ouch! Ruthlessly kill the existing SumatraPDF instances
existingProcess.Kill();
}

// Start the new instance of SumantraPDF

Process process = Process.Start(@"C:\Program Files\SumatraPDF\SumatraPDF.exe");

// wait max 5 seconds for process to be active
process.WaitForInputIdle(5000);


if (Screen.AllScreens.Length >= PREFERRED_MONITOR)
{
SetWindowPos(process.MainWindowHandle,
IntPtr.Zero,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Left,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Top,
0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}

What determines the monitor my app runs on?

Correctly written Windows apps that want to save their location from run to run will save the results of GetWindowPlacement() before shutting down, then use SetWindowPlacement() on startup to restore their position.

Frequently, apps will store the results of GetWindowPlacement() in the registry as a REG_BINARY for easy use.

The WINDOWPLACEMENTroute has many advantages over other methods:

  • Handles the case where the screen resolution changed since the last run: SetWindowPlacement() will automatically ensure that the window is not entirely offscreen
  • Saves the state (minimized/maximized) but also saves the restored (normal) size and position
  • Handles desktop metrics correctly, compensating for the taskbar position, etc. (i.e. uses "workspace coordinates" instead of "screen coordinates" -- techniques that rely on saving screen coordinates may suffer from the "walking windows" problem where a window will always appear a little lower each time if the user has a toolbar at the top of the screen).

Finally, programs that handle window restoration properly will take into account the nCmdShow parameter passed in from the shell. This parameter is set in the shortcut that launches the application (Normal, Minimized, Maximize):

if(nCmdShow != SW_SHOWNORMAL)
placement.showCmd = nCmdShow; //allow shortcut to override

For non-Win32 applications, it's important to be sure that the method you're using to save/restore window position eventually uses the same underlying call, otherwise (like Java Swing's setBounds()/getBounds() problem) you'll end up writing a lot of extra code to re-implement functionality that's already there in the WINDOWPLACEMENT functions.

Start program on a second monitor?

Save the window position before program shutdown and restore them on startup. Multimonitor displays just increase the size of the desktop; other monitor surfaces just have a different section of the same X/Y plane with its origin at the top-left of the primary monitor.

This can be done automatically for you by any of several components.

BTW, the Screen variable in the Forms unit has a property called MonitorCount and another indexable property, Monitors[Index: Integer]: TMonitor. TMonitor has properties indicating the left, top, width, height etc., so all the information you need is there.



Related Topics



Leave a reply



Submit