How can I check that a window is fully visible on the user's screen?
Asked Answered
Z

5

17

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)

Zoezoeller answered 6/7, 2011 at 2:58 Comment(0)
Z
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]
Zoezoeller answered 18/7, 2011 at 9:11 Comment(3)
I like this solution. Although, note that it should be p.X < s.Bounds.Right.Bouilli
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...Vannoy
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));Seaward
I
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.

Inclusive answered 6/7, 2011 at 4:54 Comment(3)
What will happen if you have a multi-monitor-setup?Impinge
@Impinge from the microsoft docs for Screen.GetWorkingArea at 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.Inclusive
It would appear that the first three lines do not do anything and can be just deleted.Anesthetize
P
6

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;
    }
Pitchblende answered 23/4, 2015 at 14:50 Comment(0)
R
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.

Roddy answered 19/2, 2015 at 12:22 Comment(2)
I like this solution. Because it's static, it was very easy to wrap in an attached property.Fortify
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;Sandor
N
2

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

Newson answered 6/7, 2011 at 2:59 Comment(1)
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.Inclusive

© 2022 - 2024 — McMap. All rights reserved.