前言

近期我完成了一个项目,该项目需求是在连接了双显示器的设备上,对屏幕显示设置的更改进行监听。具体而言,当显示设置调整为 “复制这些显示器” 时,程序要自动隐藏;而当显示设置变为 “扩展这些显示器” 时,程序则需显示在非主显示器的第二显示器上。接下来,我会对此次项目中的业务逻辑进行简单记录,希望能对你有所助益。

解决方案

  1. 启动程序时通过调用ShowInSecondScreen2()​方法,将窗口移动至非主显示器上面。

    private double left = 0;
    
    private Rectangle primaryRect;
    
    private Rectangle secondRect;
    
    private double top = 0;
    
    private void ShowInSecondScreen2()
    {
        this.Dispatcher.Invoke(new Action(() =>
        {
            this.WindowState = WindowState.Normal;
            //获取所有显示器的信息
            Screen[] screens = Screen.AllScreens;
    
            //确定主显示器的分辨率,用于计算副显示器的起始位置
            primaryRect = Screen.PrimaryScreen.Bounds;
    
            //将窗口放置在第一个找到的副显示器上
            bool placed = false;
            foreach (Screen screen in screens)
            {
                if (screen.Primary == false) //找到的是非主显示器
                {
                    Rectangle workingArea = screen.Bounds;
                    //设置窗口位置,使其在副显示器的左上角显示
                    left = this.Left = workingArea.Left;
                    top = this.Top = workingArea.Top;
                    this.Width = workingArea.Width;
                    this.Height = workingArea.Height;
                    secondRect = workingArea;
                    placed = true;
                    break;
                }
            }
    
            if (!placed)
            {
                // 如果没有找到副显示器,就在主显示器上显示
                this.Left = primaryRect.Left;
                this.Top = primaryRect.Top;
                this.Width = secondRect.Width;
                this.Height = secondRect.Height;
            }
            this.Activate();
        }));
    }
    
  2. 注册SystemEvents.DisplaySettingsChanged​事件,用于监听屏幕显示设置的更改。

    
    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        this.DataContext = (System.Windows.Application.Current.Resources["Locator"] as ViewModelLocator).Main;
        ShowInSecondScreen2();
        Microsoft.Win32.SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
    }
    
  3. 编写SystemEvents_DisplaySettingsChanged​事件方法,对单/双屏幕设置来控制窗口的显示位置。

    private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
    {
        try
        {
            var screens = System.Windows.Forms.Screen.AllScreens;
            if (screens.Length >= 2)
            {
                this.Show();
                this.Dispatcher.Invoke(new Action(() =>
                {
                    this.Left = left;
                    this.Top = top;
                    this.Width = secondRect.Width;
                    this.Height = secondRect.Height;
                    this.Activate();
                }));
            }
            else
            {
                this.Hide();
            }
        }
        catch (Exception exp)
        {
    
        }
    }

注意

以上代码会受到系统缩放百分比影响,建议禁用DPI感知。在AssemblyInfo.cs​中,写入禁用DPI感知代码。

//禁用DPI感知
[assembly: System.Windows.Media.DisableDpiAwareness]

原因分析

  1. DPI感知模式影响数据获取

    • 默认情况下,未声明DPI感知的应用程序会被Windows自动进行位图缩放,导致Screen.Bounds​返回逻辑分辨率而非物理分辨率。

    • 当主副显示器缩放比例不同时,Screen​类可能仅返回主显示器缩放后的逻辑分辨率,而副显示器的物理分辨率无法正确识别。

  2. Screen类的局限性

    • Screen.AllScreens​的Bounds​属性返回的是系统缩放后的逻辑分辨率,而非实际物理分辨率。

    • 在多显示器不同缩放比例场景下,未正确设置DPI感知的应用程序可能无法区分各显示器的实际分辨率。