Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine if an open WPF window is visible on any monitor

Is there a way to determine if an open WPF window is currently visible in any of the desktop's connected monitors? By visible I mean that the window's bounds rectangle intersects with the desktop rectangle of any of the monitors.

I need this functionality to determine if a window needs to be repositioned because the monitor configuration (working areas bounds, monitor count) changed between restarts of my application (which saves window positions).

I have come up with the code below and it seems to work, but it has several problems:

  1. I need to reference windows forms.
  2. I need the desktop's DPI settings and transform the windows forms actual pixels to WPF virtual pixels.
  3. I need an acutal Visual instance that already has been rendered to perform the transformation.

Do you know of a solution that gets rid of some or all of the 3 issues above?

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;

internal static class Desktop
{
    private static Size dpiFactor = new Size(1.0, 1.0);
    private static bool isInitialized;

    public static IEnumerable<Rect> WorkingAreas
    {
        get
        {
            return
                Screen.AllScreens.Select(
                    screen =>
                    new Rect(
                        screen.WorkingArea.Left * dpiFactor.Width,
                        screen.WorkingArea.Top * dpiFactor.Height,
                        screen.WorkingArea.Width * dpiFactor.Width,
                        screen.WorkingArea.Height * dpiFactor.Height));
        }
    }

    public static void TryInitialize(Visual visual)
    {
        if (isInitialized)
        {
            return;
        }

        var ps = PresentationSource.FromVisual(visual);
        if (ps == null)
        {
            return;
        }

        var ct = ps.CompositionTarget;
        if (ct == null)
        {
            return;
        }

        var m = ct.TransformToDevice;
        dpiFactor = new Size(m.M11, m.M22);
        isInitialized = true;
    }
}

Usage of the (initialized) Desktop class:

    private bool IsLocationValid(Rect windowRectangle)
    {
        foreach (var workingArea in Desktop.WorkingAreas)
        {
            var intersection = Rect.Intersect(windowRectangle, workingArea);
            var minVisible = new Size(10.0, 10.0);
            if (intersection.Width >= minVisible.Width && 
                intersection.Height >= minVisible.Height)
            {
                return true;
            }
        }

        return false;
    }

Update

Using the virtual screen (SystemParameters.VirtualScreen*) does not work because when using multiple monitors the "desktop" is not a simple rectangle. It might be a polygon. There will be blind spots in the virtual screen because

  1. the connected screens can have different resolutions
  2. you can configure the position of each screen.
like image 541
bitbonk Avatar asked May 03 '12 15:05

bitbonk


People also ask

How do I display a WPF window?

When a Window is created at run-time using the Window object, it is not visible by default. To make it visible, we can use Show or ShowDialog method. Show method of Window class is responsible for displaying a window.

How do I set the WPF window to the center of the screen?

To open a new window and have it centred on the screen, set the WindowStartupLocation property to CenterScreen. You can do this using the XAML or in code before the window is displayed. Run the program and click the button to see the result.

What is the difference between page and window in WPF?

Window is the root control that must be used to hold/host other controls (e.g. Button) as container. Page is a control which can be hosted in other container controls like NavigationWindow or Frame. Page control has its own goal to serve like other controls (e.g. Button). Page is to create browser like applications.


2 Answers

The code we use to do something similar uses information from SystemParameters, in particular SystemParameter.VirtualScreenLeft, Top, Width and Height.

If we have a saved Location and Size then we determine if the window is out of bounds like this:

bool outOfBounds =
    (location.X <= SystemParameters.VirtualScreenLeft - size.Width) ||
    (location.Y <= SystemParameters.VirtualScreenTop - size.Height) ||
    (SystemParameters.VirtualScreenLeft + 
        SystemParameters.VirtualScreenWidth <= location.X) ||
    (SystemParameters.VirtualScreenTop + 
        SystemParameters.VirtualScreenHeight <= location.Y);
like image 185
Phil Avatar answered Oct 24 '22 09:10

Phil


I use this code snipped in my WPF project on startup; it's written in vb.net:

If My.Settings.RememberWindowPositionAndSize Then
    If My.Settings.winMainWinState > 2 Then My.Settings.winMainWinState = WindowState.Normal
    If My.Settings.winMainWinState = WindowState.Minimized Then My.Settings.winMainWinState = WindowState.Normal
    Me.WindowState = My.Settings.winMainWinState
    If My.Settings.winMainWinState = WindowState.Normal Then
        Dim winBounds As New System.Drawing.Rectangle(CInt(My.Settings.winMainPosX), CInt(My.Settings.winMainPosY),
                                                      CInt(My.Settings.winMainSizeB), CInt(My.Settings.winMainSizeH))
        For Each scr As System.Windows.Forms.Screen In System.Windows.Forms.Screen.AllScreens
            If winBounds.IntersectsWith(scr.Bounds) Then
                Me.Width = My.Settings.winMainSizeB
                Me.Height = My.Settings.winMainSizeH
                Me.Top = My.Settings.winMainPosY
                Me.Left = My.Settings.winMainPosX
                Exit For
            End If
        Next
    End If
End If

and here is same (converted) code in C#

if (My.Settings.RememberWindowPositionAndSize) {
    if (My.Settings.winMainWinState > 2)
        My.Settings.winMainWinState = WindowState.Normal;
    if (My.Settings.winMainWinState == WindowState.Minimized)
        My.Settings.winMainWinState = WindowState.Normal;
    this.WindowState = My.Settings.winMainWinState;

    if (My.Settings.winMainWinState == WindowState.Normal) {
        System.Drawing.Rectangle winBounds = new System.Drawing.Rectangle(Convert.ToInt32(My.Settings.winMainPosX), Convert.ToInt32(My.Settings.winMainPosY), Convert.ToInt32(My.Settings.winMainSizeB), Convert.ToInt32(My.Settings.winMainSizeH));

        foreach (System.Windows.Forms.Screen scr in System.Windows.Forms.Screen.AllScreens) {
            if (winBounds.IntersectsWith(scr.Bounds)) {
                this.Width = My.Settings.winMainSizeB;
                this.Height = My.Settings.winMainSizeH;
                this.Top = My.Settings.winMainPosY;
                this.Left = My.Settings.winMainPosX;
                break;
            }
        }
    }
}
like image 31
Ueli Avatar answered Oct 24 '22 11:10

Ueli