Grisu47
Grisu47

Reputation: 552

get the handle of a (real) monitor by its index

Suppose I have 3 monitors. How do I get the handle of the second one only by its index? EnumDisplayMonitors() won't work because it enumerates the pseudo-devices as well and EnumDisplayDevices() doesn't give me the handle.

Upvotes: 2

Views: 7509

Answers (3)

sailfish009
sailfish009

Reputation: 2929

you can exclude primary monitor, here is sample code (styles may vary) :

if DEVMODE dmPosition x == 0 and y == 0, then it is primary monitor.

For display devices only, a POINTL structure that indicates the positional coordinates of the display device in reference to the desktop area. The primary display device is always located at coordinates (0,0).

check x, y to define second or third.

  LONG second_x=0;
  LONG second_y=0;

  DWORD deviceNum = 0;
  DISPLAY_DEVICE displayDevice;
  DEVMODE devMode;

  memset(&displayDevice, 0, sizeof(displayDevice));
  displayDevice.cb = sizeof(DISPLAY_DEVICE);
  while(EnumDisplayDevices(NULL, deviceNum, &displayDevice, 0))
  {
    EnumDisplaySettings(displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &devMode);
    if (devMode.dmPosition.x == 0 && devMode.dmPosition.y == 0)
    {
      // primary monitor
    }
    else
    {
      // second or third monitor
      second_x = devMode.dmPosition.x;
      second_y = devMode.dmPosition.y;
    }
    ++deviceNum;
  }

  m_pMainWnd->SetWindowPos(NULL,(int)second_x,(int)second_y,0,0,SWP_SHOWWINDOW | SWP_NOSIZE);

Upvotes: 2

Remy Lebeau
Remy Lebeau

Reputation: 595827

You need to use EnumDisplayMonitors() instead of EnumDisplayDevices() to access the HMONITOR handle of each monitor.

However, monitors are not identified by index. GetMonitorInfo() can tell you which monitor is "primary", but that is all. There is no way to know which monitor is "second", "third", etc. And you can't use monitor locations to determine that, either, as the "second" monitor could be positioned anywhere in relation to the "primary" monitor, and then the "third" monitor can be positioned anywhere in relation to either "first" or "second" monitor.

So you have to hope that EnumDisplayMonitors() enumerates in the order that the monitors are installed, then you can do something like this:

struct sEnumInfo
{
    int iIndex;
    HMONITOR hMonitor;
};

BOOL CALLBACK GetMonitorByIndex(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    sEnumInfo *info = (sEnumInfo*) dwData;
    if (--info->iIndex < 0)
    {
        info->hMonitor = hMonitor;
        return FALSE;
    }
    return TRUE;
}

sEnumInfo info;
info.iIndex = 1;
info.hMonitor = NULL;

EnumDisplayMonitors(NULL, NULL, GetMonitorByIndex, (LPARAM)&info);
if (info.hMonitor != NULL)
{
    //...
}

Upvotes: 6

Oleksandr Filipenko
Oleksandr Filipenko

Reputation: 31

You can enumerate devices with EnumDisplayMonitors() and check if it is pseudo monitor with EnumDisplayDevices()

While you iterating through display monitors using GetMonitorInfo() you can get MONITORINFOEX with a name of a monitor's device.

Then using EnumDisplayDevices() you can get DISPLAY_DEVICE which contains StateFlags with info if current monitor is a pseudo monitor (or as in case bellow attached to desktop)

BOOL DispayEnumeratorProc(_In_ HMONITOR hMonitor, _In_ HDC hdcMonitor, _In_ LPRECT lprcMonitor, _In_ LPARAM dwData)
{
    TClass* self = (TClass*)dwData;
    if (self == nullptr)
        return FALSE;

    MONITORINFOEX monitorInfo;
    ::ZeroMemory(&monitorInfo, sizeof(monitorInfo));
    monitorInfo.cbSize = sizeof(monitorInfo);

    BOOL res = ::GetMonitorInfo(hMonitor, &monitorInfo);
    if (res == FALSE)
        return TRUE;

    DISPLAY_DEVICE displayDevice;
    ::ZeroMemory(&displayDevice, sizeof(displayDevice));
    displayDevice.cb = sizeof(displayDevice);

    res = ::EnumDisplayDevices(monitorInfo.szDevice, 0, &displayDevice, 0);
    if (res == FALSE)
        return TRUE;

    if (displayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)
        self->RegisterDisplay(monitorInfo);

    return TRUE;
}

void TClass::EnumerateDisplayMonitors()
{
    BOOL res = ::EnumDisplayMonitors(NULL, NULL, &DispayEnumeratorProc, (LPARAM)this);
    if (res == FALSE)
        Print("Failed");
}

Also you can sort your monitors by iterating through EnumDisplayDevices()

If you pass NULL as first parameter to EnumDisplayDevices() it will return adapter's info based on second parameter. In this case your devices will have determined order.

You can compare DeviceName from DISPLAY_DEVICE with szDevice from MONITORINFOEX that you stored before to sort your HMONITORs

void TClass::SortDisplayMonitors()
{
    DISPLAY_DEVICE displayDevice;
    ::ZeroMemory(&displayDevice, sizeof(displayDevice));
    displayDevice.cb = sizeof(displayDevice);

    std::map<std::string, DWORD> devices;
    for (DWORD iDevNum = 0; ::EnumDisplayDevices(NULL, iDevNum, &displayDevice, 0) != FALSE; ++iDevNum)
        devices.insert({displayDevice.DeviceName, iDevNum});

    auto compare = [&devices](MONITORINFOEX& l, MONITORINFOEX& r)
        {
            DWORD il = -1;
            DWORD ir = -1;

            auto foundL = devices.lower_bound(l.szDevice);
            if (foundL != devices.end())
                il = foundL->second;

            auto foundR = devices.lower_bound(r.szDevice);
            if (foundR != devices.end())
                ir = foundR->second;

            return (il < ir);
        };

    std::sort(m_monitors.begin(), m_monitors.end(), compare);
}

PS: You can write
DWORD il = std::numeric_limits< DWORD >::max();
insted of
DWORD il = -1;
but don't forget to define NOMINMAX before you include Windows.h

Upvotes: 2

Related Questions