using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TradeIdeas.TIProGUI { public class ScreenFiller { /// /// Take all windows and spread them out. /// static public void FillScreens() { List
forms = new List(Application.OpenForms.Count); foreach (Form form in Application.OpenForms.Cast().Where(x => !x.IsDisposed).ToList()) if (form.Text != GuiEnvironment.TIPRO_IS_RUNNING) forms.Add(form); FillScreens(forms); } /// /// Take the given set of windows and spread them out. For example, maybe we already /// had some windows on the screen, then we loaded a layout. We might only want to /// spread out the windows we just loaded from the layout. Leave our old windows /// alone. /// /// static public void FillScreens(IEnumerable forms) { Dictionary> byScreen = new Dictionary>(); foreach (Form form in forms) { Screen screen = Screen.FromControl(form); List forThisScreen; byScreen.TryGetValue(screen, out forThisScreen); if (null == forThisScreen) { forThisScreen = new List(); byScreen.Add(screen, forThisScreen); } forThisScreen.Add(form); } foreach (var kvp in byScreen) FillScreen(kvp.Key, kvp.Value); } private static Rectangle GetRect(Form form) { return new Rectangle(form.Location, form.Size); } /// /// Spread the given forms out over the given screen. /// /// This is used internally by FillScreens(). In that case we try to keep all of the forms on the /// same screen as where they started. /// /// This might also be used when loading a layout. If someone saves a layout and sets the /// "fill screen" flag, when you load that layout you want all of those windows to go onto the /// same screen. You might have to ask the user which screen. /// /// The destination for the forms. /// The forms to adjust. public static void FillScreen(Screen screen, List forms) { if (forms.Count == 0) return; Rectangle oldLimits = GetRect(forms[0]); foreach (Form form in forms) oldLimits = Rectangle.Union(oldLimits, GetRect(form)); if ((oldLimits.Width <= 0) || (oldLimits.Height <= 0)) return; Rectangle newLimits = screen.WorkingArea; LinearEquation xRule = new LinearEquation(oldLimits.Left, newLimits.Left, oldLimits.Width, newLimits.Width); LinearEquation yRule = new LinearEquation(oldLimits.Top, newLimits.Top, oldLimits.Height, newLimits.Height); foreach (Form form in forms) { int left = xRule.F(form.Left); // The +1 -1 is an attempt to make sure that forms that were adjacent are still // adjacent. If 1 window's Right was 10, and a second window's Left was 11, we // consider those adjacent. If we try to convert 10 and 11 both from the old // coordinates to the new coordinates, there could be any number of possibilites. // There could be one or more pixes between the windows or the windows might // overlap some. The desired result is for the two windows to be adjacent // after the translation if they were before. By adding one to the Right, // then translating, then subtracting one, we are now calling F(11) twice, // rather than F(10) followed by F(11). Since the input is the same in both // cases, the output will be the same. Then we subtract 1 from the right of // the first form, so the forms will be adjacent again. // // The problem is that there is some hidden space. If the windows appear to // be adjacent, but you check the coordinates, the coordinates might say they // overlap some. We ran into the same problem with the form snapper. The // worst part is that it's hard to predict the amout of hidden space. Here // we're just pretending that there is no hidden space, which should work on // some older systems, but not on Windows 10. int right = xRule.F(form.Right+1)-1; int width = Math.Max(20, right - left); int top = yRule.F(form.Top); int bottom = yRule.F(form.Bottom+1)-1; int height = Math.Max(20, bottom - top); // TODO check if our form is out of bounds. That could happen if // it was near the bottom or right and we made it taller or wider // to meet our minimum size requirement. // TODO check if the form defines a minimum size. Use our constants // only if the form doesn't define a minimum size. /* System.Diagnostics.Debug.WriteLine(form.Name + ": " + "left " + form.Left + " -> " + left + ", " + "top " + form.Top + " -> " + top + ", " + "right " + form.Right + " -> " + right + ", " + "bottom " + form.Bottom + " -> " + bottom + ", " + "width " + form.Width + " -> " + width + ", " + "height " + form.Height + " -> " + height); */ form.Location = new Point(left, top); form.Size = new Size(width, height); } } /// /// An equation of the form y = mx + b. This is good for translating points and/or /// stretching objects PROPORTIONALLY. /// private class LinearEquation { private readonly double _m; private readonly double _b; /// /// Create this equation starting with a point and a delta. That matches the /// way we normally look at controls. Top and Height, or Left and Width. /// The independent variable is the "old" value. The dependent variable is /// the "new" value. /// /// The independent variable for our sample point. /// The dependent variable for our sample point. /// A sample change in our independent variable. /// The corresponding change in our dependent variable. public LinearEquation(int x, int y, int run, int rise) { _m = rise/(double)run; _b = y - _m * x; } /// /// Translate a value. /// /// The old value /// The new value public int F(int x) { double y =_m * x + _b; return (int)Math.Round(y); } } } }