最近做了个三维的程序,部署到客户机器上,客户看了后,现场提出这样的一个需求:程序能智能探测接入的显示器个数,当有新的显示器接入时,现有的只在一个显示器上显示的三维场景能投递到新插入的显示器上显示。类似在桌面上点击鼠标右键,选择“显示设置”菜单,弹出的如下界面:
VC++对话框工程代码实现如下:
.h文件如下:
// displayOperDlg.h: 头文件
//
#pragma once
#include<list>
using std::list;
// CDisplayOperDlg 对话框
class CDisplayOperDlg : public CDialogEx
{
// 构造
public:
CDisplayOperDlg(CWnd* pParent = nullptr); // 标准构造函数
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_DISPLAYOPER_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
void GetAllMonitors();
void SwitchPrimaryScreen(int newPrimary, int oldPrimary);
void MoveOldPrimary(int newPrimary, int oldPrimary);
void MoveNewPrimary(int newPrimary, int oldPrimary);
void CommitChange();
int GetPrimaryScreen();
int SetPrimaryScreen(int num);
int SetCloneView(int mode);
int ChangeScreenOrientation(int num, int rotation);
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnCbnSelchangeCombo1();
afx_msg void OnCbnDropdownCombo1();
afx_msg void OnBnClickedButton1();
afx_msg void OnBnClickedButton2();
afx_msg void OnBnClickedOk();
private:
list<DISPLAY_DEVICE> dev_list;
CComboBox* comboBox;
list<DEVMODE>dev_mode_list;
int PrimaryNum, selIndex, count1;
int count{0};
};
.cpp文件如下:
// displayOperDlg.cpp: 实现文件
//
#include "pch.h"
#include "framework.h"
#include "displayOper.h"
#include "displayOperDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CDisplayOperDlg 对话框
CDisplayOperDlg::CDisplayOperDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_DISPLAYOPER_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CDisplayOperDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CDisplayOperDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDOK, &CDisplayOperDlg::OnBnClickedOk)
ON_CBN_SELCHANGE(IDC_COMBO1, &CDisplayOperDlg::OnCbnSelchangeCombo1)
ON_CBN_DROPDOWN(IDC_COMBO1, &CDisplayOperDlg::OnCbnDropdownCombo1)
ON_CBN_SELCHANGE(IDC_COMBO1, &CDisplayOperDlg::OnCbnSelchangeCombo1)
ON_CBN_DROPDOWN(IDC_COMBO1, &CDisplayOperDlg::OnCbnDropdownCombo1)
ON_BN_CLICKED(IDC_BUTTON1, &CDisplayOperDlg::OnBnClickedButton1)
ON_BN_CLICKED(IDC_BUTTON2, &CDisplayOperDlg::OnBnClickedButton2)
ON_BN_CLICKED(IDOK, &CDisplayOperDlg::OnBnClickedOk)
END_MESSAGE_MAP()
// CDisplayOperDlg 消息处理程序
BOOL CDisplayOperDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
GetAllMonitors();
CComboBox* comboBox = (CComboBox*)GetDlgItem(IDC_COMBO1);
comboBox->ResetContent();
for (int i = 0; i < dev_list.size(); i++)
{
CString string1;//; = CString(i);
string1.Format(_T("%d"), i + 1);
//ZeroMemory(&string1, sizeof(string1));
//sprintf(temp, "%d", i+1);
comboBox->AddString(string1);
}
comboBox->SetCurSel(0);
UpdateData(false);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CDisplayOperDlg::GetAllMonitors()
{
std::list<DISPLAY_DEVICE> devices;
std::list<DEVMODE> modes;
int devId = 0;
bool ret = false;
bool isPrimary = false;
//list all DisplayDevices (Monitors)
do
{
DISPLAY_DEVICE displayDevice;
ZeroMemory(&displayDevice, sizeof(DISPLAY_DEVICE));
displayDevice.cb = sizeof(displayDevice);
ret = EnumDisplayDevices(NULL, devId, &displayDevice, 0);
if (ret == true)
{
if ((displayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)
{
devices.push_back(displayDevice);
isPrimary = ((displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) == DISPLAY_DEVICE_PRIMARY_DEVICE);
if (isPrimary)
PrimaryNum = devId;
}
}
devId++;
} while (ret);
dev_list = devices;
std::list<DISPLAY_DEVICE>::iterator it;
for (it = dev_list.begin(); it != dev_list.end(); it++)
{
DEVMODE deviceMode;
deviceMode.dmSize = sizeof(DEVMODE);
deviceMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_POSITION | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS; // | DM_DISPLAYORIENTATION;
EnumDisplaySettings(it->DeviceName, (int)ENUM_REGISTRY_SETTINGS, &deviceMode);
modes.push_back(deviceMode);
}
dev_mode_list = modes;
}
void CDisplayOperDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CDisplayOperDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CDisplayOperDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CDisplayOperDlg::SwitchPrimaryScreen(int newPrimary, int oldPrimary)
{
MoveNewPrimary(newPrimary, oldPrimary);
MoveOldPrimary(newPrimary, oldPrimary);
CommitChange();
}
void CDisplayOperDlg::MoveOldPrimary(int newPrimary, int oldPrimary)
{
int index = 0;
std::list<DISPLAY_DEVICE>::iterator it1;
for (it1 = dev_list.begin(); it1 != dev_list.end(); it1++)
{
if (index == oldPrimary)
break;
index++;
}
index = 0;
std::list<DEVMODE>::iterator it2;
for (it2 = dev_mode_list.begin(); it2 != dev_mode_list.end(); it2++)
{
if (index == newPrimary)
break;
index++;
}
index = 0;
std::list<DEVMODE>::iterator it3;
for (it3 = dev_mode_list.begin(); it3 != dev_mode_list.end(); it3++)
{
if (index == oldPrimary)
break;
index++;
}
it3->dmPosition.x = it2->dmPelsWidth;
it3->dmPosition.y = 0;
DEVMODE deviceMode = *it3;
int ret = ChangeDisplaySettingsEx(it1->DeviceName, &deviceMode, NULL, CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
}
void CDisplayOperDlg::MoveNewPrimary(int newPrimary, int oldPrimary)
{
int index = 0;
std::list<DISPLAY_DEVICE>::iterator it1;
for (it1 = dev_list.begin(); it1 != dev_list.end(); it1++)
{
if (index == newPrimary)
break;
index++;
}
index = 0;
std::list<DEVMODE>::iterator it2;
for (it2 = dev_mode_list.begin(); it2 != dev_mode_list.end(); it2++)
{
if (index == newPrimary)
break;
index++;
}
it2->dmPosition.x = 0;
it2->dmPosition.y = 0;
DEVMODE deviceMode = *it2;
int ret = ChangeDisplaySettingsEx(it1->DeviceName, &deviceMode, NULL, CDS_SET_PRIMARY | CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
}
void CDisplayOperDlg::CommitChange()
{
ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL);
}
int CDisplayOperDlg::GetPrimaryScreen()
{
return PrimaryNum;
}
int CDisplayOperDlg::SetPrimaryScreen(int num)
{
int oldprimary = GetPrimaryScreen();
int newPrimary = num;
if ((num >= dev_list.size()) || (num < 0))
return -1;
if (oldprimary == newPrimary)
return 0;
SwitchPrimaryScreen(newPrimary, oldprimary);
PrimaryNum = newPrimary;
return oldprimary;
}
int CDisplayOperDlg::SetCloneView(int mode)
{
/*UINT32 PathArraySize = 0;
UINT32 ModeArraySize = 0;
DISPLAYCONFIG_PATH_INFO* PathArray;
DISPLAYCONFIG_MODE_INFO* ModeArray;
DISPLAYCONFIG_TOPOLOGY_ID CurrentTopology;
//Determine the size of the path array that is required to hold all valid paths
GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &PathArraySize, &ModeArraySize); //retrieve the sizes of the DISPLAYCONFIG_PATH_INFO and DISPLAYCONFIG_MODE_INFO buffers that are required
//Allocate memory for path and mode information arrays
PathArray = (DISPLAYCONFIG_PATH_INFO*)malloc(PathArraySize * sizeof(DISPLAYCONFIG_PATH_INFO));
memset(PathArray, 0, PathArraySize * sizeof(DISPLAYCONFIG_PATH_INFO));
ModeArray = (DISPLAYCONFIG_MODE_INFO*)malloc(ModeArraySize * sizeof(DISPLAYCONFIG_MODE_INFO));
ZeroMemory(ModeArray, ModeArraySize * sizeof(DISPLAYCONFIG_MODE_INFO));
//Request all of the path information
LONG ret = QueryDisplayConfig(QDC_DATABASE_CURRENT,&PathArraySize, PathArray, &ModeArraySize, ModeArray, &CurrentTopology); //obtain the path and mode information for all posible paths
// Above CurrentTopology variable will aquire the current display setting (ie Extend, Duplicate etc)
free(PathArray);
free(ModeArray);
*/
//Set the new topology.
SetDisplayConfig(0, NULL, 0, NULL, mode | SDC_APPLY); //change to the clone topology
return 0;
}
int CDisplayOperDlg::ChangeScreenOrientation(int num, int rotation)
{
int index = 0;
std::list<DEVMODE>::iterator it;
for (it = dev_mode_list.begin(); it != dev_mode_list.end(); it++)
{
if (index == num)
break;
index++;
}
DWORD dwTemp = it->dmPelsHeight;
switch (rotation)
{
case 0:
if (it->dmDisplayOrientation == DMDO_DEFAULT)
it->dmDisplayOrientation = DMDO_90;
else if (it->dmDisplayOrientation == DMDO_90)
it->dmDisplayOrientation = DMDO_DEFAULT;
it->dmPelsHeight = it->dmPelsWidth;
it->dmPelsWidth = dwTemp;
break;
case 1:
if (it->dmDisplayOrientation == DMDO_DEFAULT)
it->dmDisplayOrientation = DMDO_90;
else if (it->dmDisplayOrientation == DMDO_90)
it->dmDisplayOrientation = DMDO_DEFAULT;
it->dmPelsHeight = it->dmPelsWidth;
it->dmPelsWidth = dwTemp;
break;
}
DEVMODE deviceMode;
ZeroMemory(&deviceMode, sizeof(deviceMode));
deviceMode.dmSize = sizeof(deviceMode);
deviceMode = *it;
index = 0;
std::list<DISPLAY_DEVICE>::iterator it1;
for (it1 = dev_list.begin(); it1 != dev_list.end(); it1++)
{
if (index == num)
break;
index++;
}
//long lRet = ChangeDisplaySettingsEx(it1->DeviceName, &deviceMode, NULL, CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
//CommitChange();
long lRet = ChangeDisplaySettingsEx(it1->DeviceName, &deviceMode, NULL, CDS_UPDATEREGISTRY, NULL);
return lRet;
}
void CDisplayOperDlg::OnCbnSelchangeCombo1()
{
//取得选中的值
CString selStr;
int selIndex = comboBox->GetCurSel();//取得选中的索引
comboBox->GetLBText(selIndex,selStr);
MessageBox(selStr);
}
void CDisplayOperDlg::OnCbnDropdownCombo1()
{
GetAllMonitors();
comboBox = (CComboBox*)GetDlgItem(IDC_COMBO1);
comboBox->ResetContent();
for (int i = 0; i < dev_list.size(); i++)
{
CString string1;//; = CString(i);
string1.Format(_T("%d"), i + 1);
//ZeroMemory(&string1, sizeof(string1));
//sprintf(temp, "%d", i+1);
comboBox->AddString(string1);
}
UpdateData(false);
}
void CDisplayOperDlg::OnBnClickedButton1()
{
long lRet;
selIndex = comboBox->GetCurSel();//取得选中的索引
if (selIndex < 0)
return;
count1++;
if (count1 % 2)
{
lRet = ChangeScreenOrientation(selIndex, 1);
}
else
{
lRet = ChangeScreenOrientation(selIndex, 0);
}
}
void CDisplayOperDlg::OnBnClickedButton2()
{
count++;
if (count % 2)
{
SetCloneView(SDC_TOPOLOGY_CLONE);
GetDlgItem(IDC_BUTTON2)->SetWindowTextW(_T("恢复"));
}
else
{
SetCloneView(SDC_TOPOLOGY_EXTEND);
GetDlgItem(IDC_BUTTON2)->SetWindowTextW(_T("屏幕复制"));
}
}
void CDisplayOperDlg::OnBnClickedOk()
{
SetPrimaryScreen(selIndex);
//
// CDialogEx::OnOK();
}
参考链接:
《VC++实现Windows中双显示器(主屏、扩展屏)各种操作的源码工程》
附上几个有用的链接:
https://msdn.microsoft.com/en-us/library/ff569533(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/dd183413(VS.85).aspx