bool MoveWindow(IntPtr window, Direction moveDir, MoveType moveType) { if (window == null) { return(false); } // Get minimized/maximized state WINDOWPLACEMENT placement; GetWindowPlacement(window, out placement); bool isMaximized = placement.showCmd == ShowCmd.Maximize; bool isMinimized = placement.showCmd == ShowCmd.Minimize; // Get window rect RECT windowRect, clientRect; GetWindowRect(window, out windowRect); GetClientRect(window, out clientRect); // Find closest rect to current position Section closestSection = null; float closestOverlap = -1f; RECT testRect = isMinimized ? placement.rcNormalPosition : windowRect; foreach (var section in Sections) { float overlap = RectangleOverlapRatio(testRect, section.rect); if (overlap > closestOverlap || (overlap == closestOverlap && ( (moveDir == Direction.Left && section.rect.Left < closestSection.rect.Left) || (moveDir == Direction.Up && section.rect.Top < closestSection.rect.Top) || (moveDir == Direction.Right && section.rect.Right > closestSection.rect.Right) || (moveDir == Direction.Down && section.rect.Bottom > closestSection.rect.Bottom)))) { closestSection = section; closestOverlap = overlap; } } Section nextSection = null; // IF window is not aligned with the closest section THEN fill that section bool fillClosest = !isMaximized && !isMinimized && moveType != MoveType.Extend && closestOverlap < .97f; if (fillClosest) { nextSection = closestSection; } else { // Window will be moving to a new section // Check for maximize if (moveDir == Direction.Up && !isMaximized && !isMinimized && (closestSection.rect.Top == closestSection.layout.top || RectangleOverlapRatio(testRect.AsRectangle(), closestSection.layout.screen.WorkingArea) > .9f)) { return(ShowWindowAsync(window, (int)ShowCmd.Maximize)); } // Check for minimize if (moveDir == Direction.Down && !isMaximized && !isMinimized && closestSection.rect.Bottom == closestSection.layout.bottom) { return(ShowWindowAsync(window, (int)ShowCmd.Minimize)); } // Check for restore if ((moveDir == Direction.Up && isMinimized) || (moveDir == Direction.Down && isMaximized)) { return(ShowWindowAsync(window, (int)ShowCmd.Restore)); } // Cache bounds of closestSection var closestBounds = closestSection.rect; if (isMaximized) { // Treat maximized as a 0-height rect at top of working area closestBounds = new RECT(closestSection.layout.screen.WorkingArea); closestBounds.Bottom = closestBounds.Top; } else if (isMinimized) { // Treat minimized as a 0-height rect at bottom of working area closestBounds = new RECT(closestSection.layout.screen.WorkingArea); closestBounds.Top = closestBounds.Bottom; } // Find best section Section bestSection = null; int bestOverlap = int.MinValue; int bestOffAxisOverlap = int.MinValue; foreach (var testSection in Sections) { testRect = testSection.rect; // Skip section where window is currently placed if (testRect.AsRectangle() == closestBounds.AsRectangle()) { continue; } bool isSameScreen = testSection.layout.screen == closestSection.layout.screen; // Moving Up while maximized or Down while minimized means window MUST change screens bool requireDifferentScreen = (isMaximized && moveDir == Direction.Up) || (isMinimized && moveDir == Direction.Down); if (requireDifferentScreen && isSameScreen) { continue; } // Moving Left/Right while Maximized OR Minimized MUST stay on the same screen bool requireSameScreen = (moveDir == Direction.Left || moveDir == Direction.Right) && (isMaximized || isMinimized); if (requireSameScreen && !isSameScreen) { continue; } // TestRect must be in direction of moveDir. UNLESS we're in a requireSameScreen special case if (!requireSameScreen) { if (moveDir == Direction.Up && testRect.Bottom > closestBounds.Top) { continue; } if (moveDir == Direction.Down && testRect.Top < closestBounds.Top) { continue; } if (moveDir == Direction.Right && testRect.Left < closestBounds.Right) { continue; } if (moveDir == Direction.Left && testRect.Right > closestBounds.Left) { continue; } } int horizOverlap = OverlapAmount(closestBounds.Left, closestBounds.Right, testRect.Left, testRect.Right); int vertOverlap = OverlapAmount(closestBounds.Top, closestBounds.Bottom, testRect.Top, testRect.Bottom); // Determine overlap along primary test axis and off axis. bool vertDir = moveDir == Direction.Up || moveDir == Direction.Down; int axisOverlap = vertDir ? horizOverlap : vertOverlap; int offAxisOverlap = Clamp(vertDir ? vertOverlap : horizOverlap, int.MinValue, 0); // We want the most overlap for our main axis if (axisOverlap < bestOverlap) { continue; } // Tie breaker for same axisOverlap (happens all the time with grids) if (bestSection != null && axisOverlap == bestOverlap) { var bestRect = bestSection.rect; // Closest for up/down if (moveDir == Direction.Up && bestRect.Bottom > testRect.Bottom) { continue; } if (moveDir == Direction.Down && bestRect.Top < testRect.Top) { continue; } if (isMaximized || isMinimized) { // Most left/right for left/right if (moveDir == Direction.Left && bestRect.Left < testRect.Left) { continue; } if (moveDir == Direction.Right && bestRect.Right > testRect.Right) { continue; } } else { // Closest for left/right if (moveDir == Direction.Left && bestRect.Left > testRect.Left) { continue; } if (moveDir == Direction.Right && bestRect.Right < testRect.Right) { continue; } } } // And we want the closestOverlap on offAxis (which is probably negative) if (offAxisOverlap < bestOffAxisOverlap) { continue; } bestSection = testSection; bestOverlap = axisOverlap; bestOffAxisOverlap = offAxisOverlap; } if (bestSection == null) { return(false); } nextSection = bestSection; } // Move to the nextSection var nextRect = nextSection.rect; // Expact rect to account for border padding GetWindowRect(window, out windowRect); GetClientRect(window, out clientRect); int pad = (windowRect.Width - clientRect.Width) / 2 + nextSection.layout.adjustSize; int xPos = nextRect.Left - pad; int yPos = nextRect.Top; int width = nextRect.Width + 2 * pad; int height = nextRect.Height + pad; RECT screenRect = new RECT(xPos, yPos, xPos + width, yPos + height); // Check for Extend operation if (moveType == MoveType.Extend && nextSection.layout.screen == closestSection.layout.screen && !isMinimized && !isMaximized) { screenRect = windowRect.Extended(screenRect); } // Convert screen coords workspace coords var screenBounds = nextSection.layout.screen.Bounds; var workspaceBounds = nextSection.layout.screen.WorkingArea; RECT workspaceRect = screenRect; // Apply workspace offset int xOffset = workspaceBounds.Left - screenBounds.Left; int yOffset = workspaceBounds.Top - screenBounds.Top; workspaceRect.Left -= xOffset; workspaceRect.Right -= xOffset; workspaceRect.Top -= yOffset; workspaceRect.Bottom -= yOffset; placement.rcNormalPosition = workspaceRect; placement.showCmd = ShowCmd.Normal; bool result = SetWindowPlacement(window, ref placement); // Windows can't handle moving to a monitor with a different scale factor. // So see if SetWindowPlacement failed to the put window where we said to put it // If it did fail, then use SetWindowPos to set pos/size // Note: We don't use SetWindowPos all the time because SetWindowPos on a maximized window // either pops (ShowCmd.Normal) or we lose the maximize flag which screws up everything :( GetWindowRect(window, out windowRect); if ((windowRect.Width != screenRect.Width || windowRect.Height != screenRect.Height)) { // Recalculate pad and associated values GetWindowRect(window, out windowRect); GetClientRect(window, out clientRect); pad = (windowRect.Width - clientRect.Width) / 2 + nextSection.layout.adjustSize; xPos = nextRect.Left - pad; yPos = nextRect.Top; width = nextRect.Width + 2 * pad; height = nextRect.Height + pad; screenRect = new RECT(xPos, yPos, xPos + width, yPos + height); // Force window to normal (in-case it was minimized or maximized) ShowWindow(window, (int)ShowCmd.Normal); // Move window to correct position. Do not change size. Do not redraw. SetWindowPos(window, new IntPtr(), screenRect.Left, screenRect.Top, screenRect.Width, screenRect.Height, (uint)SWP_FLAGS.NO_SIZE | (uint)SWP_FLAGS.NO_REDRAW); // Change Window size. Do not move. Redraw. SetWindowPos(window, new IntPtr(), screenRect.Left, screenRect.Top, screenRect.Width, screenRect.Height, (uint)SWP_FLAGS.NO_MOVE); } return(true); }