Get monitor index from its handle (HMONITOR) - c++

I'm interested in getting the monitor index (1-based, to match Windows numbering) given the monitor handle.
Case of use: given a window's rect I want to know the monitor it belongs to. I can get the handle of the monitor using MonitorFromRect:
// RECT rect
const HMONITOR hMonitor = MonitorFromRect(rect, MONITOR_DEFAULTTONEAREST);
How can I get the monitor index from this handle?
PS: not sure if duplicate, but I've been looking around with no luck.

I found this post with the opposite question: finding the handle given the index (0-based in that case).
Based on it I worked this solution:
struct sEnumInfo {
int iIndex = 0;
HMONITOR hMonitor = NULL;
};
BOOL CALLBACK GetMonitorByHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
auto info = (sEnumInfo*)dwData;
if (info->hMonitor == hMonitor) return FALSE;
++info->iIndex;
return TRUE;
}
int GetMonitorIndex(HMONITOR hMonitor)
{
sEnumInfo info;
info.hMonitor = hMonitor;
if (EnumDisplayMonitors(NULL, NULL, GetMonitorByHandle, (LPARAM)&info)) return -1;
return info.iIndex + 1; // 1-based index
}

Related

Win32: capture handle to Display monitor

I am currently developing an application that requires an HDC for each of the screens connected to the system.
I am currently using code like this:
std::vector<HDC> dcs;
HDC dcMain = ::GetDC(nullptr); // <-- don't understand this
::EnumDisplayMonitors(dcMain, nullptr, MONITORENUMPROC(&DisplayMonitorCallback), LPARAM(&dcs));
My callback is as follows:
BOOL DisplayMonitorCallback(const HMONITOR monitor, const HDC hdcMonitor, const LPRECT lprcMonitor, std::vector<HDC>& dcs)
{
dcs.push_back(hdcMonitor);
// here is where it gets weird!
HBRUSH br = CreateSolidBrush(RGB(0, 255, 0));
auto rst = FillRect(hdcMonitor, lprcMonitor, br);
// Process all monitors
return TRUE;
}
Notice that I am currently rendering a green brush on each screen. This works perfectly in THIS context (i.e. within the callback).
Now, the problem is, I am capturing those HDCs to use at a later time.
So a couple of lines later, I'm iterating over my dcs vector:
for (HDC dc : dcs)
{
HBRUSH br = CreateSolidBrush(RGB(255, 255, 0));
RECT x = { 100, 100, 500, 500 };
auto rst = FillRect(dc, &x, br);
printf("%d", rst);
}
So, my questions are:
for the dcMain, I have to pass this in, is this the good way to get one?
why does the rendering work in the callback, but does not work when I capture the HDCs and iterate over them later?
yes, and this is mentioned in the EnumDisplayMonitors() documentation:
To paint the entire virtual screen optimally for each display monitor, you can use code like this:
hdc = GetDC(NULL);
EnumDisplayMonitors(hdc, NULL, MyPaintScreenEnumProc, 0);
ReleaseDC(NULL, hdc);
the HDCs are only valid inside of the callback, as #andlabs suggested. And this makes sense, because an HDC has to be obtained and then released, but only EnumDisplayMonitors() knows how each HDC is obtained, and so only it knows how to release each one correctly. Since there is no API function for releasing an enumerated HDC, this implies that the HDCs are not valid outside of the enumeration.
MSDN tells you how to obtain an HDC for a given monitor:
HMONITOR and the Device Context
Each physical display is represented by a monitor handle of type HMONITOR. A valid HMONITOR is guaranteed to be non-NULL. A physical display has the same HMONITOR as long as it is part of the desktop. When a WM_DISPLAYCHANGE message is sent, any monitor may be removed from the desktop and thus its HMONITOR becomes invalid or has its settings changed. Therefore, an application should check whether all HMONITORS are valid when this message is sent.
Any function that returns a display device context (DC) normally returns a DC for the primary monitor. To obtain the DC for another monitor, use the EnumDisplayMonitors function. Or, you can use the device name from the GetMonitorInfo function to create a DC with CreateDC. However, if the function, such as GetWindowDC or BeginPaint, gets a DC for a window that spans more than one display, the DC will also span the two displays.
For example:
typedef std::vector<HDC> hdc_vector;
BOOL CALLBACK DisplayMonitorCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
MONITORINFOEX mi = {0};
mi.cbSize = sizeof(mi);
if (GetMonitorInfo(hMonitor, &mi))
{
HDC dc = CreateDC(NULL, mi.szDevice, NULL, NULL);
if (dc)
reinterpret_cast<hdc_vector*>(dwData)->push_back(dc);
}
...
return TRUE;
}
hdc_vector dcs;
EnumDisplayMonitors(dcMain, nullptr, DisplayMonitorCallback, reinterpret_cast<LPARAM>(&dcs));
...
for (HDC dc : dcs)
{
...
}
...
for (HDC dc : dcs)
DeleteDC(dc);
Since you are clearly using C++11, I would suggest using std::unique_ptr for the memory management of the HDCs so you don't have to call DeleteDC() manually. And I would use a lambda for the callback, and change the std::vector to a std::map (so you can lookup the HDC of any specific monitor when needed):
typedef std::unique_ptr<std::remove_pointer<HDC>::type, decltype(::DeleteDC)> device_hdc;
typedef std::map<HMONITOR, device_hdc> device_hdc_map;
device_hdc_map dcs;
EnumDisplayMonitors(dcMain, nullptr,
[](HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL {
MONITORINFOEX mi = {0};
mi.cbSize = sizeof(mi);
if (GetMonitorInfo(hMonitor, &mi))
{
HDC dc = CreateDC(NULL, mi.szDevice, NULL, NULL);
if (dc)
(*reinterpret_cast<device_hdc_map*>(dwData))[hMonitor] = device_hdc(dc, &::DeleteDC);
}
...
return TRUE;
},
reinterpret_cast<LPARAM>(&dcs)
);
...
for (device_hdc_map::value_type &dc : dcs)
{
// use dc.second.get() (the actual HDC) as needed ...
}

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.
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)
{
//...
}
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
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);

How to Get Tooltip of a Window From Window Handle? [duplicate]

I'm looking for a way to get the tooltip control (if any) which is associated with a given HWND. The text of the tooltip control would be sufficient, too. The closest thing I found is the TTM_GETTEXT message, but it's meant to be sent to the tooltip control itself instead of the tool it's associated with. I don't have a handle to the tooltip control though. Does anybody know how to do this?
All this is done using plain Windows API in C++.
There doesn't seem to be a specific message to get the tip or its text from the control, but this is how MFC's CWnd class implements OnToolHitTest(), which you should be able to adapt to Win32:
INT_PTR SomeFunction(HWND hWndChild, TOOLINFO *pTI)
{
if (hWndChild != NULL) // Your HWND being tested
{
// return positive hit if control ID isn't -1
INT_PTR nHit = _AfxGetDlgCtrlID(hWndChild);
// Replace with GetDlgCtrlID().
// hits against child windows always center the tip
if (pTI != NULL && pTI->cbSize >= sizeof(AFX_OLDTOOLINFO))
{
// setup the TOOLINFO structure
pTI->hwnd = m_hWnd;
pTI->uId = (UINT_PTR)hWndChild;
pTI->uFlags |= TTF_IDISHWND;
pTI->lpszText = LPSTR_TEXTCALLBACK;
// set TTF_NOTBUTTON and TTF_CENTERTIP if it isn't a button
if (!(::SendMessage(hWndChild, WM_GETDLGCODE, 0, 0) & DLGC_BUTTON))
pTI->uFlags |= TTF_NOTBUTTON|TTF_CENTERTIP;
}
return nHit;
}
return -1; // not found
}
Hopefully this will be useful.
To get tooltip text from some control you could use TTN_NEEDTEXT message. It was designed to be used by the ToolTip control, but I cannot see any reason why you could not send it from other place.
You could enumerate the windows looking for a tooltip control that has a parent of the required window. You'll need to supply the window together with the tool id (normally from GetDlgCtrlID)...:
HWND hToolTipWnd = NULL;
BOOL GetToolTipText(HWND hWnd, UINT nId, std::wstring& strTooltip)
{
hToolTipWnd = NULL;
EnumWindows(FindToolTip, (LPARAM)hWnd);
if (hToolTipWnd == NULL)
return FALSE;
WCHAR szToolText[256];
TOOLINFO ti;
ti.cbSize = sizeof(ti);
ti.hwnd = hWnd;
ti.uId = nId;
ti.lpszText = szToolText;
SendMessage(hToolTipWnd, TTM_GETTEXT, 256, (LPARAM)&ti);
strTooltip = szToolText;
return TRUE;
}
BOOL CALLBACK FindToolTip(HWND hWnd, LPARAM lParam)
{
WCHAR szClassName[256];
if (GetClassName(hWnd, szClassName, 256) == 0)
return TRUE;
if (wcscmp(szClassName, L"tooltips_class32") != 0)
return TRUE;
if (GetParent(hWnd) != (HWND)lParam)
return TRUE;
hToolTipWnd = hWnd;
return FALSE;
}
I don't know if the window whose tooltip you want to retrieve is a child of a window you have created.
If this is the case, you can handle the NM_TOOLTIPSCREATED notification, which is sent by a child window to its parent when it creates a tooltip (or should be sent: it is true for common controls but I don't know for other kinds of windows). This notification includes a handle to the tooltip window.

How do I get the number of displays in windows?

I want to count the number of active displays. For Mac I can use the following:
CGDisplayCount nDisplays;
CGGetActiveDisplayList(0,0, &nDisplays);
log.printf("Displays connected: %d",(int)nDisplays);
How can I achieve the same in Windows? I've found EnumDisplayMonitors but I can't work out how to use it.
As you have discovered, EnumDisplayMonitors() will do the job but it is a little tricky to call. The documentation states:
The EnumDisplayMonitors function enumerates display monitors (including invisible pseudo-monitors associated with the mirroring drivers) that intersect a region formed by the intersection of a specified clipping rectangle and the visible region of a device context. EnumDisplayMonitors calls an application-defined MonitorEnumProc callback function once for each monitor that is enumerated. Note that GetSystemMetrics (SM_CMONITORS) counts only the display monitors.
This leads us to an easier solution: GetSystemMetrics(SM_CMONITORS). Indeed this may be even better than EnumDisplayMonitors() if you have psuedo-monitors.
As illustration of calling EnumDisplayMonitors() try this:
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
int *Count = (int*)dwData;
(*Count)++;
return TRUE;
}
int MonitorCount()
{
int Count = 0;
if (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&Count))
return Count;
return -1;//signals an error
}
Not tested, but essentially you only need to provide the callback for the enum function:
int numMonitors = 0;
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
//lprcMonitor holds the rectangle that describes the monitor position and resolution)
numMonitors++;
return true;
}
int main()
{
EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, NULL);
}
The problem with above approaches is that they do not detect the correct number of monitors in Clone, Internal, External topologies. They get the correct number only in Extended topology. There is a way to get the correct number of monitors in any topology with QueryDisplayConfig function. You have to use QDC_ALL_PATHS in flags and count only unique monitors found.
DISPLAYCONFIG_TOPOLOGY_ID currTopologyId = 0;
UINT32 numPathArrayElements = 0;
UINT32 numModeInfoArrayElements = 0;
LONG retCode = ::GetDisplayConfigBufferSizes(flags, &numPathArrayElements, &numModeInfoArrayElements);
auto pathArray = std::make_unique<DISPLAYCONFIG_PATH_INFO[]>(numPathArrayElements);
auto modeInfoArray = std::make_unique<DISPLAYCONFIG_MODE_INFO[]>(numModeInfoArrayElements);
retCode = ::QueryDisplayConfig(flags, &numPathArrayElements, pathArray.get(), &numModeInfoArrayElements, modeInfoArray.get(), &currTopologyId);
Use DisplayConfigGetDeviceInfo to get the monitor name from the path target.
DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = {};
targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
targetName.header.size = sizeof(targetName);
targetName.header.adapterId = pathInfo.targetInfo.adapterId;
targetName.header.id = pathInfo.targetInfo.id;
LONG retCode = ::DisplayConfigGetDeviceInfo(&targetName.header);
You will get the monitor name in:
targetName.monitorDevicePath, targetName.monitorFriendlyDeviceName

How to find whether system has the font I needed in MFC?

I want to write the following function
bool IsFontExistInSystem(const CString& fontStyle) const
{
}
Is there any API in windows to do this?
Many Thanks!
Here's some old code I dug out that will check if a font is installed. It could do with being tidied up but you get the idea:
static int CALLBACK CFontHelper::EnumFontFamExProc(ENUMLOGFONTEX* /*lpelfe*/, NEWTEXTMETRICEX* /*lpntme*/, int /*FontType*/, LPARAM lParam)
{
LPARAM* l = (LPARAM*)lParam;
*l = TRUE;
return TRUE;
}
bool Font::IsInstalled(LPCTSTR lpszFont)
{
// Get the screen DC
CDC dc;
if (!dc.CreateCompatibleDC(NULL))
{
return false;
}
LOGFONT lf = { 0 };
// Any character set will do
lf.lfCharSet = DEFAULT_CHARSET;
// Set the facename to check for
_tcscpy(lf.lfFaceName, lpszFont);
LPARAM lParam = 0;
// Enumerate fonts
::EnumFontFamiliesEx(dc.GetSafeHdc(), &lf, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)&lParam, 0);
return lParam ? true : false;
}
You could use EnumFontFamiliesEx to find whether exist actual font.
UPD: I've just learned that it is recommended by MS to use EnumFontFamiliesEx instead of EnumFontFamilies.