初级代码游戏的专栏介绍与文章目录-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
这个函数用来改变窗口位置。
(这里是文档结束)
还没有评论,来说两句吧...