【问题标题】:Determine if an open WPF window is visible on any monitor确定打开的 WPF 窗口是否在任何监视器上可见
【发布时间】:2012-05-03 15:17:13
【问题描述】:

有没有办法确定打开的 WPF 窗口当前是否在任何桌面连接的监视器中可见?可见是指窗口的边界矩形与任何监视器的桌面矩形相交。

我需要此功能来确定是否需要重新定位窗口,因为监视器配置(工作区域边界、监视器计数)在我的应用程序重新启动(保存窗口位置)之间发生了变化。

我想出了下面的代码,它似乎可以工作,但它有几个问题:

  1. 我需要参考 windows 窗体。
  2. 我需要桌面的 DPI 设置并将窗口窗体实际像素转换为 WPF 虚拟像素。
  3. 我需要一个已经渲染的实际 Visual 实例来执行转换。

您知道解决上述三个问题的部分或全部的解决方案吗?

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

(初始化的)Desktop 类的用法:

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

更新

使用虚拟屏幕 (SystemParameters.VirtualScreen*) 不起作用,因为当使用多个显示器时,“桌面”不是一个简单的矩形。它可能是一个多边形。虚拟屏幕会有盲点,因为

  1. 连接的屏幕可以有不同的分辨率
  2. 您可以配置每个屏幕的位置。

【问题讨论】:

  • 尽管该链接中的问题非常相似,但它并没有回答我的问题。我已经提出了一个解决方案,我想找到一种方法来明确摆脱我提到的三个问题。
  • 那么 WPF 和标准 C# 库无法帮助您。您可以尝试 PInvoke,但这可能更脏。有关更多信息,请参阅此问题:stackoverflow.com/questions/1927540/…
  • 引用 System.Windows.Forms 有什么问题?这不像您要向垃圾箱添加另一个程序集。我个人的偏好是创建一个使用 PInvoke 的帮助器类,并保持代码干净可读。

标签: wpf interop desktop dpi


【解决方案1】:

我们用于执行类似操作的代码使用来自SystemParameters 的信息,特别是 SystemParameter.VirtualScreenLeft、Top、Width 和 Height。

如果我们保存了位置和大小,那么我们确定窗口是否超出范围,如下所示:

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

【讨论】:

  • 使用虚拟屏幕不起作用,因为当使用多个显示器时,“桌面”不是一个简单的矩形。虚拟屏幕会有盲点,因为a)连接的屏幕可以有不同的分辨率,b)您可以配置每个屏幕的位置。
【解决方案2】:

我在启动时使用在我的 WPF 项目中截取的这段代码;它是用 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

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

【讨论】:

  • 现在 (Win8+) 每个屏幕都可以有自己的 DPI 设置。 WPF 使用逻辑 96 DPI 像素,因此对于任何不使用小字体 = 96 DPI 的屏幕,您的代码都会失败。
【解决方案3】:

此代码检查窗口左上角是否位于虚拟屏幕框(包含所有可用屏幕的矩形)内。它还负责坐标可能为负数的多显示器设置,例如右侧或底部的主显示器。

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;

【讨论】:

  • 使用虚拟屏幕不起作用,因为当使用多个显示器时,“桌面”不是一个简单的矩形。虚拟屏幕会有盲点,因为a)连接的屏幕可以有不同的分辨率,b)您可以配置每个屏幕的位置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-08
  • 2011-02-09
  • 2018-01-02
  • 2013-05-10
相关资源
最近更新 更多