16

Is there a way to check that a WinForm is fully visible on the screen (eg is not out of bounds of the screen?)

I've tried using SystemInformation.VirtualScreen for this, which works great as long as the virtual screen is a rectangle, but as soon as it's not (eg 3 screens in a L shape), SystemInformation.VirtualScreen returns the smallest rectangle containing all the visible pixels (so a window on the upper right corner of the L won't be visible although it's in the virtual screen)


The reason I'm trying to achieve this is that I'd like my program to open its child windows in the last location they were on, but I don't want those window to be out of view if the user changes is setup (eg unplugs the extra screen from his laptop)

Brann
  • 31,689
  • 32
  • 113
  • 162

5 Answers5

15

Here's how I eventually did it :

bool isPointVisibleOnAScreen(Point p)
{
    foreach (Screen s in Screen.AllScreens)
    {
        if (p.X < s.Bounds.Right && p.X > s.Bounds.Left && p.Y > s.Bounds.Top && p.Y < s.Bounds.Bottom)
            return true;
    }
    return false;
}

bool isFormFullyVisible(Form f)
{
    return isPointVisibleOnAScreen(new Point(f.Left, f.Top)) && isPointVisibleOnAScreen(new Point(f.Right, f.Top)) && isPointVisibleOnAScreen(new Point(f.Left, f.Bottom)) && isPointVisibleOnAScreen(new Point(f.Right, f.Bottom));
 }

There might be some false positive if the user has a "hole" in his display setup (see example below) but I don't think any of my users will ever be in such a situation :)

   [1]
[2][X][3]
Avi Turner
  • 10,234
  • 7
  • 48
  • 75
Brann
  • 31,689
  • 32
  • 113
  • 162
  • 4
    I like this solution. Although, note that it should be `p.X < s.Bounds.Right`. – Anders Carstensen May 09 '13 at 08:48
  • can be simplified to `if(s.Bounds.Contains(p))` And maybe `s.WorkingArea` can be better when it comes to multi-monitor, bounds are not consistent... – joe Dec 14 '20 at 04:36
  • I really like this solution, definite +1. I used LINQ instead of the `foreach` loop in this way: `var result = Screen.AllScreens.Any(x => x.Bounds.Contains(point));` – Nate W Aug 29 '22 at 18:51
7

Here's how I would do it:

This will move the control (form) inside the Display bounds as close as it can to the original location.

    private void EnsureVisible(Control ctrl)
    {
        Rectangle ctrlRect = ctrl.DisplayRectangle; //The dimensions of the ctrl
        ctrlRect.Y = ctrl.Top; //Add in the real Top and Left Vals
        ctrlRect.X = ctrl.Left;
        Rectangle screenRect = Screen.GetWorkingArea(ctrl); //The Working Area fo the screen showing most of the Ctrl

        //Now tweak the ctrl's Top and Left until it's fully visible. 
        ctrl.Left += Math.Min(0, screenRect.Left + screenRect.Width - ctrl.Left - ctrl.Width);
        ctrl.Left -= Math.Min(0, ctrl.Left - screenRect.Left);
        ctrl.Top += Math.Min(0, screenRect.Top + screenRect.Height - ctrl.Top - ctrl.Height);
        ctrl.Top -= Math.Min(0, ctrl.Top - screenRect.Top);

    }

Of course to answer your original question instead of moving the control you could just check if any of the 4 Math.Min's returned something other than 0.

deepee1
  • 12,878
  • 4
  • 30
  • 43
  • What will happen if you have a multi-monitor-setup? – Sam Apr 18 '12 at 13:33
  • 1
    @Sam from the microsoft docs for Screen.GetWorkingArea at http://msdn.microsoft.com/en-us/library/45zswy9x.aspx it states "Retrieves the working area for the display that contains the largest region of the specified control." basically move the control to be fully inside the screen it is mostly inside. – deepee1 Apr 18 '12 at 19:11
  • It would appear that the first three lines do not do anything and can be just deleted. – GSerg Dec 12 '19 at 10:51
5

This is my solution. It solves the "hole" problem.

    /// <summary>
    /// True if a window is completely visible 
    /// </summary>
    static bool WindowAllVisible(Rectangle windowRectangle)
    {
        int areaOfWindow = windowRectangle.Width * windowRectangle.Height;
        int areaVisible = 0;
        foreach (Screen screen in Screen.AllScreens)
        {
            Rectangle windowPortionOnScreen = screen.WorkingArea;
            windowPortionOnScreen.Intersect(windowRectangle);
            areaVisible += windowPortionOnScreen.Width * windowPortionOnScreen.Height;
            if (areaVisible >= areaOfWindow)
            {
                return true;
            }
        }
        return false;
    }
SirTechington
  • 51
  • 1
  • 3
4

I was trying to do this exact same thing detect if the window opened off screen then accordingly reposition it to the nearest location where it was previously found at. I look all over the internet and nothing worked from all the solutions people offered.

So i took it upon myself to make my own class that does just exactly this and it works 100%.

Here is my code

public static class ScreenOperations
{
    public static bool IsWindowOnAnyScreen(Window Window, short WindowSizeX, short WindowSizeY, bool AutoAdjustWindow)
    {
        var Screen = System.Windows.Forms.Screen.FromHandle(new WindowInteropHelper(Window).Handle);

        bool LeftSideTest = false, TopSideTest = false, BottomSideTest = false, RightSideTest = false;

        if (Window.Left >= Screen.WorkingArea.Left)
            LeftSideTest = true;

        if (Window.Top >= Screen.WorkingArea.Top)
            TopSideTest = true;

        if ((Window.Top + WindowSizeY) <= Screen.WorkingArea.Bottom)
            BottomSideTest = true;

        if ((Window.Left + WindowSizeX) <= Screen.WorkingArea.Right)
            RightSideTest = true;

        if (LeftSideTest && TopSideTest && BottomSideTest && RightSideTest)
            return true;
        else
        {
            if (AutoAdjustWindow)
            {
                if (!LeftSideTest)
                    Window.Left = Window.Left - (Window.Left - Screen.WorkingArea.Left);

                if (!TopSideTest)
                    Window.Top = Window.Top - (Window.Top - Screen.WorkingArea.Top);

                if (!BottomSideTest)
                    Window.Top = Window.Top - ((Window.Top + WindowSizeY) - Screen.WorkingArea.Bottom);

                if (!RightSideTest)
                    Window.Left = Window.Left - ((Window.Left + WindowSizeX) - Screen.WorkingArea.Right);
            }
        }

        return false;
    }
}

Usage: ScreenOperations.IsWindowOnAnyScreen(WPFWindow, WPFWindowSizeX, WPFWindowSizeY, true); this will check if the window is offscreen at all, that being 1 pixel under the taskbar or 1 pixel off the users current monitor.

It detects which monitor the window if on first so it should work with multi-monitors.

this method returns true if the window is on the screen and false if its not.

The last parameter is for auto adjusting the window to the nearest part on the screen for you. if you put false for that parameter it will not adjust the window for you.

So this is a complete WPF solution to this issue, and WinForm converting should be easy if you know how to do it, Change Window to Form and FromHandle(Form.Handle) should work.

  • I like this solution. Because it's static, it was very easy to wrap in an attached property. – gregsdennis Sep 29 '15 at 13:41
  • Works nicely, thank you. The simple math can be simplifed even more though: if (!leftSideTest) window.Left = screen.WorkingArea.Left; if (!topSideTest) window.Top = screen.WorkingArea.Top; if (!bottomSideTest) window.Top = screen.WorkingArea.Bottom - sizeY; if (!rightSideTest) window.Left = screen.WorkingArea.Right - sizeX; – Jan Zeman Jun 04 '20 at 19:23
2

Check whether Screen.AllScreens.Any(s => s.WorkingArea.Contains(rect))

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 6
    This method is a good start but if you have multiple monitors and the screen is split between the both of them then you will get a False positive. – deepee1 Jul 06 '11 at 04:35