Binary Fortress
Binary Fortress Software
CheckCentral
ClipboardFusion
CloudShow
CloudShow Manager
DisplayFusion
FileSeek
HashTools
LogFusion
Notepad Replacer
Online Base64 Decoder
Online Base64 Encoder
Online JSON Formatter
ShellSend
TrayStatus
VoiceBot
WallpaperFusion
Window Inspector
More Apps...
DisplayFusion
CheckCentral
CloudShow
ClipboardFusion
FileSeek
TrayStatus
VoiceBot
WallpaperFusion
Display
Fusion
by Binary Fortress Software
Download
Download
Change Log
Download Beta
Beta Change Log
License (EULA)
Features
Free vs Pro
More
Screenshots
Scripted Functions (Macros)
Languages
Help
Help Guide
FAQ
Discussions
Contact Us
Find My License
Mailing Address
Advanced Settings
Purchase
Login / Register
WARNING: You currently have Javascript disabled!
This website will not function correctly without Javascript enabled.
Title
Message
OK
Confirm
Yes
No
Automatically Span Window Across Two Monitors
Return to DisplayFusion Scripted Functions (Macros)
Description
Finds the two best monitors to span a window across based on alignment and resolution then spans it across them.
Language
C#.net
Minimum Version
9.6.1+
Created By
Derek Ziemba
Contributors
-
Date Created
Mar 12, 2020
Date Last Modified
Mar 10, 2022
Scripted Function (Macro) Code
Copy
Select All
using System; using System.Drawing; // Finds the two best monitors to span a window across based on alignment and resolution then spans it across them. // * Saves the previous position (lifetime of window), so 2nd click of the assign title button restores previous position // * Doesn't care if your monitors switched ids because of a driver update, etc - which breaks DisplayFusions regular Custom Functions // * Works over remote Desktop. The built in Custom Functions never get alignment right when there's any kind of monitor mismatch. // * Only restores previous position if that looks like what's intended // ex: If you accidentally move the window slightly, it will instead re-doublewide the window. // Written by Derek Ziemba // PS: Any plans to update the DisplayFusion compiler? The lack of modern features tripped me up a bit. public static class DisplayFusionFunction { private const string KeyPrefix = "DoubleWide_"; private const string KeyLastUsedDate = KeyPrefix + "LastUsedDate"; // ref was intentionally used instead of 'out'. If it fails I don't want to overwrite the result, because in this use case that rectangle is the doublewide dimensions we still may want to apply private static void LoadPriorSize(IntPtr handle, ref Rectangle result) { string prevstr = BFS.ScriptSettings.ReadValue(KeyPrefix + handle.ToInt32().ToString()); if (!String.IsNullOrWhiteSpace(prevstr)) { var arr = prevstr.Split(','); // TryParse intentionally avoided. If an error occurs here, I'd like to know why something didn't work. result = new Rectangle(Int32.Parse(arr[0]), Int32.Parse(arr[1]), Int32.Parse(arr[2]), Int32.Parse(arr[3])); } // Attempt to do some garbage collection // I don't want my registery becoming litered with irrelevant entires DateTime dateLastInvoked = default(DateTime); if (DateTime.TryParse(BFS.ScriptSettings.ReadValue(KeyLastUsedDate), out dateLastInvoked)) { if (dateLastInvoked.AddHours(24) < DateTime.UtcNow) { // Am assuming this only deletes entries related to this script // A way to specify a LifeTimePolicy for saved values would be nice. Like on next restart, x days, etc. BFS.ScriptSettings.DeleteAllValues(); return; } } } private static void SaveCurrentSize(IntPtr handle, Rectangle rect) { BFS.ScriptSettings.WriteValue(KeyLastUsedDate, DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm")); var data = String.Join(",", new Int32[] { rect.X, rect.Y, rect.Width, rect.Height }); // The handle is used to make the keys unique. // Titles, like shown in examples, can change depending on what webpage your on. // But the handle should be consistent for the lifetime of the window. BFS.ScriptSettings.WriteValue(KeyPrefix + handle.ToInt32().ToString(), data); } private static bool AreSimilarSizeAndAlignment(ref Rectangle a, ref Rectangle b, Int32 sizeSlop, Int32 ySlop, Int32 xSlop) { return Math.Abs(a.Width - b.Width) <= sizeSlop && Math.Abs(a.Height - b.Height) <= sizeSlop && Math.Abs(a.Y - b.Y) <= ySlop && Math.Abs(a.X - b.X) <= xSlop; } public static void Run(IntPtr windowHandle) { if (windowHandle == IntPtr.Zero) { return; } Rectangle[] monitors = BFS.Monitor.GetMonitorWorkAreas(); if (monitors.Length < 2) { BFS.Dialog.ShowMessageError("Requires at least 2 monitors."); return; } MonitorPair pair = new MonitorPair(); // Try to find the best pair of monitors to span across. Slowly relax the requirements when a suitable pair can't be found bool found = pair.InitializePair(ref monitors, 5, 5) || pair.InitializePair(ref monitors, 20, 35) || pair.InitializePair(ref monitors, 25, 50) || pair.InitializePair(ref monitors, 300, 300); if (!found) { BFS.Dialog.ShowMessageError("No Monitors of similar size and alignment found in consecutive horizontal order.\nSpanning a window across them would look dumb and not be practical.\n" + monitors.Inspect(", ")); return; } //BFS.Dialog.ShowMessageInfo(monitors.Inspect(", ")); //BFS.Dialog.ShowMessageInfo(pair.Inspect()); Rectangle current = BFS.Window.GetBounds(windowHandle); Rectangle target = pair.ToRect(); if (AreSimilarSizeAndAlignment(ref current, ref target, 4, 4, 4)) { // We're already pretty much fullsize, so they want to undo doublewide LoadPriorSize(windowHandle, ref target); // if the call BFS.Window.SetSizeAndLocation(windowHandle, target.X, target.Y, target.Width, target.Height); } else if (AreSimilarSizeAndAlignment(ref current, ref target, 20, 300, 300)) { // User may have accidentally moved the window and just wants it to be full double size again // So Don't save/overwrite whatever size it's currently at BFS.Window.SetSizeAndLocation(windowHandle, pair.X, pair.Y, pair.Width, pair.Height); } else { SaveCurrentSize(windowHandle, current); BFS.Window.SetSizeAndLocation(windowHandle, pair.X, pair.Y, pair.Width, pair.Height); } } private struct MonitorPair { public Rectangle First; public Rectangle Second; public Int32 X { get { return Math.Min(this.First.X, this.Second.X); } } public Int32 Y { get { return Math.Max(this.First.Y, this.Second.Y); } } public Int32 Top { get { return Math.Min(this.First.Top, this.Second.Top); } } public Int32 Bottom { get { return Math.Max(this.First.Bottom, this.Second.Bottom); } } public Int32 Width { get { return this.Second.Right - this.First.Left; } } public Int32 Height { get { return this.Bottom - this.Top; } } public bool InitializePair(ref Rectangle[] monitors, Int32 sizeSlop, Int32 alignmentSlop) { this.First = monitors[0]; for (var i = 1; i < monitors.Length; i++) { this.Second = monitors[i]; if (AreSimilarSizeAndAlignment(ref First, ref Second, sizeSlop, alignmentSlop, alignmentSlop + Second.Height)) { return true; } this.First = this.Second; } return false; } public Rectangle ToRect() { return new Rectangle(this.X, this.Y, this.Width, this.Height); } public override string ToString() { return this.Inspect(); } } private static string Inspect<T>(this T input) { var sb = new System.Text.StringBuilder("{ ", 128); var props = typeof(T).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty); foreach (var prop in props) { if (prop.PropertyType.IsPrimitive) { sb.Append("\n ").Append(prop.Name).Append(": ").Append(prop.GetValue(input)).Append(", "); } } sb.Remove(sb.Length - 2, 2); return sb.Append("\n}").ToString(); } private static string Inspect<T>(this T[] input, string separator) { var ls = new System.Collections.Generic.List<string>(input.Length); foreach (var value in input) { ls.Add(value.Inspect()); } return String.Join(separator, ls); } }