When we test VS Anywhere in a production environment, one of the things that we tend to do is to open 2 sessions of Visual Studio on either side of the screen, so we can make quick tests of specific features.
This requires opening 2 different instances of visual studio by hand, and attach each side of the screen, something tedious, boring, and, above all, automatable. So I set out to find the way of solving this small issue.
The path
If we have Windows 7 or higher we can make use of Aero Snap, using Win+Left or Win+Right, it sets a window to the left or to the right of the screen. It seemed simple, I would only need to emulate this key combination, something that it seemed easy to do:
Theorically, we can solve this by using Powershell. In fact, in the following entry of TechNet Provide Input to Applications with PowerShell explains how to send commands to an application using SendKeys, resulting in the following code:
add-type -AssemblyName microsoft.VisualBasic add-type -AssemblyName System.Windows.Forms Calc start-sleep -Milliseconds 500 [Microsoft.VisualBasic.Interaction]::AppActivate("Calc") [System.Windows.Forms.SendKeys]::SendWait("1{ADD}1=")
According to the last line, we should be able to send to the program any combination of keys, so the next step was to find the Windows key in the SendKeys reference: http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.send(v=vs.110) .aspx
Surprise: is not there. On the other hand, looking a bit online, I found that the Windows keystroke could be simulated by using the Ctrl + Esc.
Second surprise: IT SIMULATES the WINDOWS key, and nothing else. When you press that combination, It jumps right to the desktop or to the start menu, it is not possible to use it to simulate a key combination involving Win + whatever, so that by this way little more we can do.
On the other hand, if what we are doing is a keyboard shortcut, surely matches some kind of command available on the Windows API that makes the action of Aero Snap, but no, there is no API, no documentation and there is no reference except the brand name, so this is another dead end.
There was still another option, which was to move the window directly using a custom program, after some search I found this piece of code, allowing you to do what I wanted using Powershell:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Win7 Powershell script to resize Minecraft to 1280×720 (HD for fraps youtube capture) | |
# use by typing the following at a command prompt: | |
# > PowerShell -ExecutionPolicy Bypass -File minecraft-sethd.ps1 | |
# refs: | |
# http://stackoverflow.com/questions/2556872/how-to-set-foreground-window-from-powershell-event-subscriber-action | |
# http://richardspowershellblog.wordpress.com/2011/07/23/moving-windows/ | |
# http://www.suite101.com/content/client-area-size-with-movewindow-a17846 | |
Add-Type @" | |
using System; | |
using System.Runtime.InteropServices; | |
public class Win32 { | |
[DllImport("user32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); | |
[DllImport("user32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); | |
[DllImport("user32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); | |
} | |
public struct RECT | |
{ | |
public int Left; // x position of upper-left corner | |
public int Top; // y position of upper-left corner | |
public int Right; // x position of lower-right corner | |
public int Bottom; // y position of lower-right corner | |
} | |
"@ | |
$rcWindow = New-Object RECT | |
$rcClient = New-Object RECT | |
$h = (Get-Process | where {$_.MainWindowTitle -eq "minecraft"}).MainWindowHandle | |
[Win32]::GetWindowRect($h,[ref]$rcWindow) | |
[Win32]::GetClientRect($h,[ref]$rcClient) | |
$width = 1280 | |
$height = 720 | |
$dx = ($rcWindow.Right – $rcWindow.Left) – $rcClient.Right | |
$dy = ($rcWindow.Bottom – $rcWindow.Top) – $rcClient.Bottom | |
[Win32]::MoveWindow($h, $rct.Left, $rct.Top, $width + $dx, $height + $dy, $true ) |
It was not too complex, but I chose to do a small application with C# so that it could solve the problem, using native calls to the Windows API through P/Invoke:
Importing functions and moving windows
To use the Windows API functions we have to copy their header in our function, define it as external (.NET framework will already know what to do) and define the DLL from which we import, in this case the function MoveWindow is located in user32.dll:
[DllImport ("user32.dll")] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
We will have to repeat this process for each function that we want to import, a we will also need to add the following using:
using System.Runtime.InteropServices;
To be able to move the window, we need its hWnd, which is a unique window identifier. There are many ways to locate it, and one of them is from the list of processes, selecting those whose main window matched with what was looking for, in this case “Visual Studio”.
foreach (Process proc in Process.GetProcesses () { if (proc.MainWindowTitle.Contains ("Visual Studio")) { IntPtr handle = proc.P:System.Diagnostics.process.MainWindowHandle; ... MoveWindow(handle...); ... } }
For moving the window, we need to set an origin and a size, and for this we could either fix them manually, or rely on the resolution of the screen, using the following functions:
Screen.PrimaryScreen.WorkingArea.Width; Screen.PrimaryScreen.WorkingArea.Height;
These are found within the namespace System.Windows.Forms, which requires in addition a reference as System.Drawing from our project.
The really interesting thing about this, is that it allows us to move the window to the position we would like with any size, so we can take advantage of it in order to set any number of windows, being these equally distributed.
Finally we need a couple of additional calls to Windows API functions to maximize the window before moving it, and to set the focus, which are imported in a similar way:
[DllImport ("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int X); [DllImport ("user32.dll")] public static extern bool SetFocus(IntPtr hWnd);
Launching applications and input parameters
In addition to the functionality of moving windows, it could be interesting to launch the apps we want to move, so a bit of extra code to generate a process, to set the name of the file, and, after launching it, a small sleep while loading:
Process p = new Process(); p.StartInfo.FileName = "notepad.exe"; p.Start (); System.Threading.Thread.Sleep (2000);
To make it simpler, the last step is to convert all the parameters used to input arguments of the program, being finally the project a console application, which gets, in this order;
- Path of the executable
- Title of the window to search
- Number of processes to launch
- Waiting for each process
In the case of a 3 windows launch for notepad.exe, this is the output showing the console application:
On the other hand we have 3 windows, equally distributed:
As the last detail, the window title is set using the following code block:
Console.Title = "WinResize";
Wrapping up
This is a small app that solves a very specific scenario can be widely improved and I’ll probably work for some more time on it, here are some ideas for future improvements:
- Make all the script in powershell, it’s possible, and we can make P/Invoke without problems
- Emulate the keypress (must be a code associated with the windows key, and a way to press it!) simulating the keyboard.
- Rather than take all processes available, use the ID of those we’ve just created.
The code is available for free and licensed under open source. If you want to contribute please send your pull request :)
Enjoy!
Additional links
- On Github: source code
- On Stack Overflow: Programmatically Maximize Window On Half Of Screen
- On Stack Overflow: Get the handle of a window with not fully known title.(C#)
- MSDN: Console.Title
- MSDN: SendKeys.Send
- On Github: Resizing a Minecraft Window