Determine si una ventana WPF abierta es visible en cualquier monitor

¿Hay alguna manera de determinar si una ventana WPF abierta está actualmente visible en alguno de los monitores conectados del escritorio? Por visible quiero decir que el rectángulo de los límites de la ventana se cruza con el rectángulo del escritorio de cualquiera de los monitores.

Necesito esta funcionalidad para determinar si es necesario cambiar la posición de una ventana porque la configuración del monitor (límites de las áreas de trabajo, número de monitores) cambió entre los reinicios de mi aplicación (que guarda las posiciones de la ventana).

Se me ocurrió el siguiente código y parece funcionar, pero tiene varios problemas:

  1. Necesito hacer referencia a formularios de Windows.
  2. Necesito la configuración de DPI del escritorio y transformar los píxeles reales de las ventanas en píxeles virtuales WPF.
  3. Necesito una instancia visual real que ya se haya renderizado para realizar la transformación.

¿Conoce alguna solución que elimine algunos o todos los 3 problemas anteriores?

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;
    }
}

Uso de (inicializado) Desktop clase:

    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;
    }

Noticias

Usando la pantalla virtual (SystemParameters.VirtualScreen*) no funciona porque cuando se usan varios monitores, el "escritorio" no es un simple rectángulo. Podría ser un polígono. Habrá puntos ciegos en la pantalla virtual porque

  1. las pantallas conectadas pueden tener diferentes resoluciones
  2. puede configurar la posición de cada pantalla.

preguntado el 03 de mayo de 12 a las 16:05

Aunque la pregunta de ese enlace es bastante similar, no responde a mi pregunta. Ya propuse una solución y me gustaría encontrar una manera de deshacerme de los tres problemas que mencioné. -

Entonces, las bibliotecas WPF y C# estándar no pueden ayudarlo. Podrías probar PInvoke, pero eso probablemente sea más sucio. Consulte esta pregunta para obtener más información al respecto: stackoverflow.com/questions/1927540/… -

¿Cuál es el problema con la referencia a System.Windows.Forms? No es como si estuviera agregando otro ensamblaje al contenedor. Mi preferencia personal es crear una clase auxiliar que use PInvoke y mantenga el código limpio y legible. -

3 Respuestas

El código que usamos para hacer algo similar usa información de Parámetros del sistema, en particular SystemParameter.VirtualScreenLeft, Top, Width y Height.

Si tenemos una ubicación y un tamaño guardados, determinamos si la ventana está fuera de los límites de esta manera:

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);

contestado el 26 de mayo de 12 a las 18:05

El uso de la pantalla virtual no funciona porque cuando se usan varios monitores, el "escritorio" no es un simple rectángulo. Habrá puntos ciegos en la pantalla virtual porque a) las pantallas conectadas pueden tener diferentes resoluciones yb) puedes configurar la posición de cada pantalla. - bitbonk

Uso este código cortado en mi proyecto WPF al inicio; está escrito en 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

y aquí está el mismo código (convertido) en 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;
            }
        }
    }
}

Respondido 20 Jul 17, 14:07

Hoy en día (Win8+) cada pantalla puede tener su propia configuración de DPI. WPF usa píxeles lógicos de 96 DPI, por lo que su código fallará en cualquier pantalla que no use fuentes pequeñas = 96 DPI. - elástico76

Este código verifica si la esquina superior izquierda de una ventana está dentro del cuadro Pantalla virtual (el rectángulo que incluye todas las pantallas disponibles). También se ocupa de las configuraciones de varios monitores en las que las coordenadas pueden ser negativas, por ejemplo, el monitor principal a la derecha o en la parte inferior.

bool valid_position =
SystemParameters.VirtualScreenLeft <= saved_location.X &&
(SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth) >= saved_location.X &&
SystemParameters.VirtualScreenTop <= saved_location.Y &&
(SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight) >= saved_location.Y;

Respondido el 21 de diciembre de 16 a las 13:12

El uso de la pantalla virtual no funciona porque cuando se usan varios monitores, el "escritorio" no es un simple rectángulo. Habrá puntos ciegos en la pantalla virtual porque a) las pantallas conectadas pueden tener diferentes resoluciones yb) puedes configurar la posición de cada pantalla. - bitbonk

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.