C++检测多显示器并把窗口显示在不同显示器上(完整源码)

C++检测多显示器并把窗口显示在不同显示器上(完整源码)

码农世界 2024-06-19 后端 136 次浏览 0个评论

初级代码游戏的专栏介绍与文章目录-CSDN博客

        早先大部分应用都不考虑多显示的问题。

        如果是多窗口应用,子窗口不会被限制在父窗口里面的,可以轻松把窗口拖到不同的显示器上。

        但是很多流行的界面都是一个全屏主窗口,然后其他窗口都只能在主窗口范围内,这种程序就没法自动适应多显示器了。

        但是现在专门针对多显示器的需求增多了,比如视频监控类的应用,用户会很喜欢使用多显示器,像这样就需要程序支持多显示器,并且最好是自动使用多显示器而不需要用户拖放窗口。

目录

一、多显示器概要

二、获取显示器数量和坐标

三、显示窗口到副显示器

四、相关技术点

4.1 EnumDisplayDevices

4.2  EnumDisplaySettingsEx

4.3 GetSystemMetrics(SM_CMONITORS)

4.4 MFC的MoveWindow


一、多显示器概要

        多显示器共享同一个屏幕坐标空间,主显示器左上角为(0,0),其余显示器排列在其它位置,坐标可能是负值。具体如何取决于显示设置,可以用鼠标拖放显示器来控制显示器的位置关系。

        多个显示器的坐标可能会有间隔!原因是窗口最大化时阴影和边框位于显示器外面,但是又不能出现在别的显示器上,所以显示器之间有间隔。这些问题可以通过获取窗口坐标来验证。

二、获取显示器数量和坐标

        获取所有显示器信息的代码:

RECT m_ScrRect[10];
int GetScreenRect()
{
	int count = 0;
	for (int ScreenNo = 0;true; ++ScreenNo)
	{
		BOOL flag;
		DISPLAY_DEVICE dd;
		ZeroMemory(&dd, sizeof(dd));
		dd.cb = sizeof(dd);
		
		//枚举显示器,获取后面要用的名字,注意这会返回系统所能支持的所有显示器,ScreenNo从0开始,直到返回FALSE
		flag = EnumDisplayDevices(NULL, ScreenNo, &dd, EDD_GET_DEVICE_INTERFACE_NAME);
		if (!flag)
		{
			break;
		}
		
		DEVMODE dm;
		ZeroMemory(&dm, sizeof(dm));
		dm.dmSize = sizeof(dm);
		//返回当前设置,如果失败表明显示器不在线
		flag = EnumDisplaySettingsEx(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm, 0);
		if (!flag)
		{
			continue;
		}
		m_ScrRect[count].left = dm.dmPosition.x;//如果副显示器在左边,则这个值是负的
		m_ScrRect[count].top = dm.dmPosition.y;
		m_ScrRect[count].right = m_ScrRect[count].left + dm.dmPelsWidth - 1;
		m_ScrRect[count].bottom = m_ScrRect[count].top + dm.dmPelsHeight - 1;
		++count;
		thelog << dd.DeviceName << " (dmBitsPerPel "< 

        注意这段代码假设显示器最多有10个。thelog是日志,endi表示一般信息,可以用cout和endl代替。后面的测试代码会用这个函数获取多显示器信息。

        相关技术点:

  • EnumDisplayDevices 枚举系统所有的显示器,这是系统所支持的最大数量,每个都预先分配了名字
  • EnumDisplayDevices 获取显示器信息,如果显示器不存在就会返回失败,但是存在的显示器并不是连续的,所以每个都要检查

    三、显示窗口到副显示器

            修改显示about对话框的代码(对话框项目),显示在副显示器上:

    //这里改成了非模态对话框,用来验证多显示器支持,里面记录的奇怪的问题都是因为项目设置没有设置“每个监视器高DPI识别”导致的
    CAboutDlg dlgAbout;
    void CTestDlg::OnSysCommand(UINT nID, LPARAM lParam)
    {
    	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    	{
    		//奇怪的问题,在副屏上如果已经是最大化,再次打开就会消失,但是主屏不会
    		//计算得到的RECT和实测的不一样,实测的x似乎大一倍
    		//由于尺寸问题,最大化可以在副屏显示,但是要先隐藏,不然再次打开就消失,而且再也不会出现
    		if (!dlgAbout.GetSafeHwnd())
    		{
    			dlgAbout.Create(IDD_ABOUTBOX, this);
    			
    		}
    		else { thelog << "无模式对话框已存在" << ende; }
    		int monitors = GetSystemMetrics(SM_CMONITORS);//这个直接返回显示器个数
    		if (GetScreenRect() != monitors)
    		{
    			thelog << "检索显示器信息出错" << ende;
    		}
    		else
    		{
    			RECT* pMonitorRect = &m_ScrRect[monitors - 1];//取最后一个显示器
    			RECT rect;
    			dlgAbout.GetWindowRect(&rect);
    			rect.right = pMonitorRect->left + rect.right - rect.left;
    			rect.bottom = pMonitorRect->top + rect.bottom - rect.top;
    			rect.left = pMonitorRect->left;
    			rect.top = pMonitorRect->top;
    			thelog << "目标 " << rect.left << " " << rect.top << " " << rect.right << " " << rect.bottom << endi;
    			dlgAbout.MoveWindow(&rect);
    			//很奇怪,这样就不显示,最大化就显示SW_SHOWMAXIMIZED
    			if (0 == dlgAbout.ShowWindow(SW_SHOW))thelog << "ShowWindow成功" << endi;
    			else thelog << "ShowWindow已经是显示的" << ende;
    			RECT curr;
    			dlgAbout.GetWindowRect(&curr);
    			thelog << "实际" << curr.left << " " << curr.top << " " << curr.right << " " << curr.bottom << endi;
    		}
    		if (0==dlgAbout.ShowWindow(SW_SHOW))thelog << "ShowWindow成功" << endi;
    		else thelog << "ShowWindow已经是显示的" << ende;
    		
    	}
    	else
    	{
    		CDialogEx::OnSysCommand(nID, lParam);
    	}
    }
    

            这个代码自动把窗口显示在副显示器上,并测试了放大缩小等窗口操作。

            自动显示到副显示器很简单,就是把窗口移动到副显示器的坐标范围内而已。 

            以上代码是win10+VS2017环境的。

    四、相关技术点

    4.1 EnumDisplayDevices

    winuser.h
    User32.dll/User32.lib
    BOOL EnumDisplayDevicesA(
      [in]  LPCSTR           lpDevice,
      [in]  DWORD            iDevNum,
      [out] PDISPLAY_DEVICEA lpDisplayDevice,
      [in]  DWORD            dwFlags
    );

            这个函数列举所有设备,知道设备索引超出最大值。所以调用要这个函数需要一个循环,并不断递增iDevNum,直到调用失败。但每个成功返回的设备并不一定是在线的,看起来就是系统预先准备了所支持的最大数量的入口。

    4.2  EnumDisplaySettingsEx

    Winuser.h
    User32.dll/User32.lib
    BOOL EnumDisplaySettingsExA(
      [in]  LPCSTR   lpszDeviceName,
      [in]  DWORD    iModeNum,
      [out] DEVMODEA *lpDevMode,
      [in]  DWORD    dwFlags
    );

            这个函数获取具体一个显示器的信息,设备名来自之前的枚举过程,显示器不在线就会失败。系统分配显示器索引并不是连续的,所以要逐个检查之前用EnumDisplayDevices获得的有效设备名。

    4.3 GetSystemMetrics(SM_CMONITORS)

    Winuser.h
    User32.dll/User32.lib
    int GetSystemMetrics(
      [in] int nIndex
    );

            这个调用直接获取显示器个数,但是不能获取显示器信息。

            参数nIndex指示不同的功能,比如SM_CMOUSEBUTTONS表示鼠标按钮数量。

    4.4 MFC的MoveWindow

            这个函数用来改变窗口位置。

    (这里是文档结束)

转载请注明来自码农世界,本文标题:《C++检测多显示器并把窗口显示在不同显示器上(完整源码)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,136人围观)参与讨论

还没有评论,来说两句吧...

Top