You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

WPF应用中如何在复制(镜像)与扩展显示模式间平滑切换

WPF应用中如何在复制(镜像)与扩展显示模式间平滑切换

嘿,我完全懂你的需求——主应用在第一屏全屏运行,第二屏固定显示等待图片,点击特定按钮切换到复制(镜像)模式,完成操作后再切回扩展模式继续显示等待图。因为WPF本身没有直接控制系统显示模式的API,咱们得调用Windows原生的User32.dll来实现,下面给你一步步拆解方案:

第一步:导入Windows原生API

首先得在项目里导入User32.dll的相关函数和结构体,显示模式是系统级设置,WPF管不到这一层,得靠系统API来操作:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Linq;

public static class DisplayModeManager
{
    // 导入必要的系统API函数
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern long ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, uint dwflags, IntPtr lParam);

    // 定义枚举显示器的回调委托
    private delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);

    // 显示器信息结构体
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct MONITORINFOEX
    {
        public int cbSize;
        public RECT rcMonitor;
        public RECT rcWork;
        public uint dwFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szDeviceName;
    }

    // 矩形区域结构体
    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    // 显示模式配置结构体
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct DEVMODE
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string dmDeviceName;
        public short dmSpecVersion;
        public short dmDriverVersion;
        public short dmSize;
        public short dmDriverExtra;
        public uint dmFields;
        public int dmPositionX;
        public int dmPositionY;
        public uint dmDisplayOrientation;
        public uint dmDisplayFixedOutput;
        public short dmColor;
        public short dmDuplex;
        public short dmYResolution;
        public short dmTTOption;
        public short dmCollate;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string dmFormName;
        public short dmLogPixels;
        public uint dmBitsPerPel;
        public uint dmPelsWidth;
        public uint dmPelsHeight;
        public uint dmDisplayFlags;
        public uint dmDisplayFrequency;
        public uint dmICMMethod;
        public uint dmICMIntent;
        public uint dmMediaType;
        public uint dmDitherType;
        public uint dmReserved1;
        public uint dmReserved2;
        public uint dmPanningWidth;
        public uint dmPanningHeight;
    }

    // 常量定义
    private const int DM_POSITION = 0x00000020;
    private const int CDS_UPDATEREGISTRY = 0x00000001;
    private const int CDS_NORESET = 0x00000010;
    private const int DISP_CHANGE_SUCCESSFUL = 0;
}

第二步:实现切换到复制模式的方法

复制模式的核心是让第二显示器和主显示器位置重合,实现镜像显示。我们先找到主显示器,再把其他显示器的位置设为主显示器的原点:

public static bool SwitchToDuplicateMode()
{
    IntPtr primaryMonitor = IntPtr.Zero;
    MONITORINFOEX primaryInfo = new MONITORINFOEX();
    primaryInfo.cbSize = Marshal.SizeOf(primaryInfo);

    // 枚举所有显示器,定位主显示器
    EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMonitor, hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) =>
    {
        MONITORINFOEX info = new MONITORINFOEX();
        info.cbSize = Marshal.SizeOf(info);
        if (GetMonitorInfo(hMonitor, ref info))
        {
            if ((info.dwFlags & 1) == 1) // 主显示器的标志位
            {
                primaryMonitor = hMonitor;
                primaryInfo = info;
            }
        }
        return true;
    }, IntPtr.Zero);

    if (primaryMonitor == IntPtr.Zero) return false;

    bool success = true;
    // 遍历非主显示器,设置为镜像位置
    EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMonitor, hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) =>
    {
        if (hMonitor != primaryMonitor)
        {
            MONITORINFOEX info = new MONITORINFOEX();
            info.cbSize = Marshal.SizeOf(info);
            if (GetMonitorInfo(hMonitor, ref info))
            {
                DEVMODE devMode = new DEVMODE();
                devMode.dmSize = (short)Marshal.SizeOf(devMode);
                devMode.dmDeviceName = info.szDeviceName;
                devMode.dmFields = DM_POSITION;
                devMode.dmPositionX = 0; // 和主显示器同原点,实现镜像
                devMode.dmPositionY = 0;

                long result = ChangeDisplaySettingsEx(info.szDeviceName, ref devMode, IntPtr.Zero, CDS_UPDATEREGISTRY | CDS_NORESET, IntPtr.Zero);
                if (result != DISP_CHANGE_SUCCESSFUL)
                {
                    success = false;
                }
            }
        }
        return success;
    }, IntPtr.Zero);

    // 应用所有设置
    if (success)
    {
        ChangeDisplaySettingsEx(null, ref new DEVMODE(), IntPtr.Zero, 0, IntPtr.Zero);
    }

    return success;
}

第三步:实现切换回扩展模式的方法

要切回扩展模式,得先在应用启动时保存各显示器的原始位置,这样才能准确恢复:

// 保存扩展模式下的显示器配置
private static List<(string DeviceName, int X, int Y)> _extendedModeConfig = new List<(string, int, int)>();

// 初始化时保存扩展模式的原始配置
public static void SaveExtendedModeConfig()
{
    _extendedModeConfig.Clear();
    EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMonitor, hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) =>
    {
        MONITORINFOEX info = new MONITORINFOEX();
        info.cbSize = Marshal.SizeOf(info);
        if (GetMonitorInfo(hMonitor, ref info))
        {
            _extendedModeConfig.Add((info.szDeviceName, info.rcMonitor.left, info.rcMonitor.top));
        }
        return true;
    }, IntPtr.Zero);
}

// 切换回扩展模式
public static bool SwitchToExtendedMode()
{
    if (_extendedModeConfig.Count == 0) return false;

    bool success = true;
    foreach (var config in _extendedModeConfig)
    {
        DEVMODE devMode = new DEVMODE();
        devMode.dmSize = (short)Marshal.SizeOf(devMode);
        devMode.dmDeviceName = config.DeviceName;
        devMode.dmFields = DM_POSITION;
        devMode.dmPositionX = config.X;
        devMode.dmPositionY = config.Y;

        long result = ChangeDisplaySettingsEx(config.DeviceName, ref devMode, IntPtr.Zero, CDS_UPDATEREGISTRY | CDS_NORESET, IntPtr.Zero);
        if (result != DISP_CHANGE_SUCCESSFUL)
        {
            success = false;
            break;
        }
    }

    if (success)
    {
        ChangeDisplaySettingsEx(null, ref new DEVMODE(), IntPtr.Zero, 0, IntPtr.Zero);
    }

    return success;
}

第四步:结合你的WPF窗口逻辑使用

在主窗口里完成初始化、按钮事件绑定,以及等待窗口的显示逻辑:

public MainWindow()
{
    InitializeComponent();
    // 启动时保存扩展模式的原始配置
    DisplayModeManager.SaveExtendedModeConfig();
    // 初始化第二屏的等待窗口
    InitializeWaitingWindow();
}

private Window _waitingWindow;

private void InitializeWaitingWindow()
{
    _waitingWindow = new Window
    {
        WindowStyle = WindowStyle.None,
        WindowState = WindowState.Maximized,
        // 替换成你的等待图片路径
        Background = new ImageBrush(new BitmapImage(new Uri("Images/waiting-screen.jpg", UriKind.Relative)))
    };
    // 将窗口放到第二屏
    var secondScreen = Screen.AllScreens.FirstOrDefault(s => !s.Primary);
    if (secondScreen != null)
    {
        _waitingWindow.Left = secondScreen.Bounds.Left;
        _waitingWindow.Top = secondScreen.Bounds.Top;
        _waitingWindow.Show();
    }
}

// 切换到复制模式的按钮点击事件
private void SwitchToDuplicateBtn_Click(object sender, RoutedEventArgs e)
{
    // 隐藏等待窗口,复制模式下两屏显示内容一致
    _waitingWindow.Hide();
    bool success = DisplayModeManager.SwitchToDuplicateMode();
    if (!success)
    {
        MessageBox.Show("切换到复制模式失败!");
        // 失败后重新显示等待窗口
        _waitingWindow.Show();
    }
}

// 完成操作后切回扩展模式的方法
private void SwitchBackToExtended()
{
    bool success = DisplayModeManager.SwitchToExtendedMode();
    if (success)
    {
        // 重新将等待窗口放到第二屏并显示
        var secondScreen = Screen.AllScreens.FirstOrDefault(s => !s.Primary);
        if (secondScreen != null)
        {
            _waitingWindow.Left = secondScreen.Bounds.Left;
            _waitingWindow.Top = secondScreen.Bounds.Top;
            _waitingWindow.Show();
        }
    }
    else
    {
        MessageBox.Show("切换回扩展模式失败!");
    }
}

注意事项

  • 记得在项目里添加System.Windows.Forms程序集引用,不然Screen类会报错;
  • 切换显示模式可能会因为系统权限或硬件限制失败,建议加上异常处理;
  • 测试时确保有两个显示器连接,否则第二屏的等待窗口不会显示。

备注:内容来源于stack exchange,提问作者Minh Huynh

火山引擎 最新活动