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.
Related
In a standard C++/MFC MDI doc/view project, I want to implement a tracking tooltip in the view (the tabbed view windows which generally occupy most of the main frame window). So, in class MyAppView, I have a member CToolTipCtrl tooltip. Function MyAppView::OnInitialUpdate() contains the initialization
BOOL ok0 = tooltip.Create(this, TTS_ALWAYSTIP);
CRect clientRect; GetClientRect(&clientRect);
BOOL ok2 = tooltip.AddTool(this, LPSTR_TEXTCALLBACK, &clientRect, 1234/*tool ID*/);
tooltip.Activate(TRUE);
to make the entire client area of the view be the "tool". The message map contains an entry
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnNeedToolTipText)
and the function OnNeedToolTipText is defined as
BOOL MyAppView::OnNeedToolTipText(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
{
UNREFERENCED_PARAMETER(id);
NMTTDISPINFO *pTTT = (NMTTDISPINFO *)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
BOOL bRet = FALSE;
if(nID == 1234)
{
// Come here when text is needed for tracking tooltip
}
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if(nID)
{
_stprintf_s(pTTT->szText, sizeof(pTTT->szText) / sizeof(TCHAR),
_T("Control ID = %d"), nID);
pTTT->hinst = AfxGetResourceHandle();
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
}
What happens is that only placing the mouse on the menu items (File, Edit, View, Window, Help) causes the code to enter OnNeedToolTipText, with an ID of 0-5. Moving the mouse into the client area (the view) does nothing.
How can I get the tooltip to appear in the client area of the view only?
Visual Studio 2017; C++; 64-bit Windows 7
In order to solve the problem you need to do the following:
BOOL CMyAppView::PreTranslateMessage(MSG* pMsg)
{
switch (pMsg->message)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_MOUSEMOVE:
if (m_pToolTip->GetSafeHwnd () != NULL)
{
m_pToolTip->RelayEvent(pMsg);
}
break;
}
return CScrollView::PreTranslateMessage(pMsg);
}
If you want a tracking tooltip in a view, these are the steps to follow:
Create tooltip and add the tool.
void CToolTipDemoView::OnInitialUpdate()
{
// ...
m_toolTip.Create(this, TTS_ALWAYSTIP | TTS_NOANIMATE);
m_toolTip.AddTool(this, _T("Doesn't matter"));
}
Handle WM_MOUSEMOVE message. First, call _TrackMouseEvent in order to further receive WM_MOUSELEAVE and activate the tooltip. Second, update the tooltip text, and show it at mouse pointer coordinates.
void CToolTipDemoView::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bTrackingMouseLeave)
{
TRACKMOUSEEVENT tme = { 0 };
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = m_hWnd;
::_TrackMouseEvent(&tme);
m_toolTip.Activate(TRUE);
m_bTrackingMouseLeave = TRUE;
}
if (m_pointLastMousePos != point)
{
CString strText;
strText.Format(_T("x = %d y = %d"), point.x, point.y);
m_toolTip.UpdateTipText(strText, this);
m_toolTip.Popup();
m_pointLastMousePos = point;
}
CScrollView::OnMouseMove(nFlags, point);
}
Handle WM_MOUSELEAVE and deactivate the tooltip.
void CCToolTipDemoView::OnMouseLeave()
{
m_bTrackingMouseLeave = FALSE;
// mouse pointer leaves the window so deactivate the tooltip
m_toolTip.Activate(FALSE);
CScrollView::OnMouseLeave();
}
Notes:
there is no more necessary to handle TTN_NEEDTEXT.
also, there is no more necessary to override PreTranslateMessage
So I went back to see what I could be missing. I wrote this stuff over 10 years ago. I had also overridden a CWnd member
virtual INT_PTR OnToolHitTest( CPoint point, TOOLINFO* pTI ) const;
With:
INT_PTR HERichView::OnToolHitTest( CPoint point, TOOLINFO* pTI ) const
{
pTI->hwnd = m_hWnd;
pTI->uId = point.x + ( point.y << 16 );
CRect rect;
GetClientRect( rect );
pTI->rect= rect;
pTI->lpszText= LPSTR_TEXTCALLBACK;
return pTI->uId;
}
And I checked, it won't work without this. So your:
ON_NOTIFY_EX( TTN_NEEDTEXT, 0, OnToolTip )
Should get called if you add the above. And only EnableToolTips( ); Should be needed.
I have not succeeded in getting the tracking tooltip to work within MFC. The closest I have come is
In message map: ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnNeedToolTipText)
In OnInitialUpdate: BOOL ok1 = EnableTrackingToolTips(TRUE);
In override of virtual function OnToolHitTest:
pTI->hwnd = m_hWnd;
pTI->uId = (UINT_PTR)m_hWnd;
pTI->uFlags = TTF_IDISHWND | TTF_ALWAYSTIP | TTF_TRACK | TTF_NOTBUTTON | TTF_ABSOLUTE | TTF_SUBCLASS;
pTI->lpszText = LPSTR_TEXTCALLBACK;
return pTI->uId;
In OnNeedToolTipText:
NMTTDISPINFO *pTTT = (NMTTDISPINFO *)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
BOOL bRet = FALSE;
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if(nID)
{
CURSORINFO ci; ci.cbSize = sizeof(CURSORINFO); // get something interesting to display
GetCursorInfo(&ci);
_stprintf_s(pTTT->szText, sizeof(pTTT->szText) / sizeof(TCHAR),
_T("Control ID = %lld at (%d, %d)"), nID, ci.ptScreenPos.x, ci.ptScreenPos.y);
pTTT->hinst = AfxGetResourceHandle();
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
This produces the following peculiar behavior. When I start the app and move the mouse cursor into the client area of the CScrollView, a tooltip appears right next to the cursor.
If I move the mouse carefully (smoothly) the tooltip tracks properly. After a while, though, it disappears, and no further mouse motions, including leaving the CScrollView window and returning, make it re-appear.
I think what is happening is that when the mouse cursor moves over the tooltip window, the tooltip is turned off, permanently. This disappearance does not seem to be time-related (e g, due to auto-pop); if the mouse is left untouched, the tooltip remains indefinitely.
Is it possible to get all windows hwnd from a Alt + Tab window - excluding Metro? Maybe there is some alternative for Windows 8?
I was trying to get all windows using EnumWindows function and paste hwnd's to the GetAltTabInfo function and it is not working for me. I get error message: "Invalid window handle" from GetLastError, because this function (GetAltTabInfo) is no longer usable when you've got Aero enabled. This conclusion is from here: GetAltTabInfo usage?.
Using "Which windows appear in the Alt+Tab list?" article by Raymond Chen, I have been able to reproduce the window list.
// See http://blogs.msdn.com/b/oldnewthing/archive/2007/10/08/5351207.aspx
BOOL IsAltTabWindow(HWND hwnd)
{
// Start at the root owner
HWND hwndWalk = GetAncestor(hwnd, GA_ROOTOWNER);
// See if we are the last active visible popup
HWND hwndTry;
while ((hwndTry = GetLastActivePopup(hwndWalk)) != hwndTry) {
if (IsWindowVisible(hwndTry)) break;
hwndWalk = hwndTry;
}
return hwndWalk == hwnd;
}
BOOL CALLBACK CbEnumAltTab(HWND hwnd, LPARAM lParam)
{
// Do not show invisible windows
if (!IsWindowVisible(hwnd))
return TRUE;
// Alt-tab test as described by Raymond Chen
if (!IsAltTabWindow(hwnd))
return TRUE;
const size_t MAX_WINDOW_NAME = 256;
TCHAR windowName[MAX_WINDOW_NAME];
if (hwnd == GetShellWindow())
_tcscpy_s(windowName, MAX_WINDOW_NAME, _T("Desktop")); // Beware of localization
else
GetWindowText(hwnd, windowName, MAX_WINDOW_NAME);
// Do not show windows that has no caption
if (0 == windowName[0])
return TRUE;
// Print found window to debugger's output
const size_t MAX_MESSAGE_NAME = 64 + MAX_WINDOW_NAME;
TCHAR message[MAX_MESSAGE_NAME];
_stprintf_s(message, MAX_MESSAGE_NAME, _T("AltTab: %08X %s\n"), hwnd, windowName);
OutputDebugString(message);
return TRUE;
}
void ListAltTabWindows()
{
EnumWindows(CbEnumAltTab, 0);
}
Notes:
metro seems to be excluded already
Didn't check WS_EX_TOOLWINDOW
Didn't check WS_EX_APPWINDOW.
Didn't test extensively
I'm trying to manipulate a specific Internet Explorer 11 window. Using WinSpy++ I find that
The top level window's class is an IEFrame with the title of the document as the text (as returned by GetWindowText)
The actual web view class is called "Internet Explorer_Server" and is a child of the former.
I wrote a simple test case for finding the web view of IE11 opened on "https://encrypted.google.com/" in three different ways:
HWND FindIE_A()
{
// Use FindWindow, works!
HWND hWndTop = ::FindWindowA( NULL, "Google - Internet Explorer" );
// Find the web view window, the callback (FindIEServer) is NEVER called!
HWND hWnd = NULL;
::EnumChildWindows( hWndTop, &FindIEServer, (LPARAM)&hWnd );
return hWnd;
}
HWND FindIE_B()
{
// Use EnumChildWindows with NULL as parent, works!
HWND hWndTop = NULL;
::EnumChildWindows( NULL, &FindIEMain, (LPARAM)&hWndTop );
// Find the web view window, the callback (FindIEServer) is NEVER called!
HWND hWnd = NULL;
::EnumChildWindows( hWndTop, &FindIEServer, (LPARAM)&hWnd );
return hWnd;
}
HWND FindIE_C()
{
// Simple EnumWindows, works!
HWND hWndTop = NULL;
::EnumWindows( &FindIEMain, (LPARAM)&hWndTop );
// Find the web view window, the callback (FindIEServer) is NEVER called!
HWND hWnd = NULL;
::EnumChildWindows( hWndTop, &FindIEServer, (LPARAM)&hWnd );
return hWnd;
}
The callbacks that are very simple; get a property from the window and compare against a hard-coded value:
BOOL CALLBACK FindIEServer( HWND hWnd, LPARAM lParam )
{
char className[64];
::GetClassNameA( hWnd, className, sizeof(className) );
if ( !strcmp( className, "Internet Explorer_Server" ) )
{
*(HWND*)lParam = hWnd;
return FALSE;
}
return TRUE;
}
BOOL CALLBACK FindIEMain( HWND hWnd, LPARAM lParam )
{
char text[128];
::GetWindowTextA( hWnd, text, sizeof(text) );
if ( !strcmp( text, "Google - Internet Explorer" ) )
{
*(HWND*)lParam = hWnd;
return FALSE;
}
return TRUE;
}
EnumChildWindows failed (by not calling the callback AT ALL!) every time when provided with a parent window. Why?
The problem is that when I look for the window title, I was assuming there was only one window with that title. However Internet Explorer does some shenanigans and creates multiple windows with the same title however only one of them has the class IEFrame.
It just so happens that the first window found was the wrong one, it didn't have any children (and thus EnumChildWindows doesn't have anything to iterate over). Just adding an extra check for title + class works.
However as #wakjah suggested, it is better integrate IE (or any other browser) directly into your code. With google I found lots of documentation on how to do this with both IE and Chrome.
I am creating a modeless property sheet using the following settings:
PROPSHEETHEADER pshdr = { 0 };
pshdr.dwSize = sizeof(PROPSHEETHEADER);
pshdr.dwFlags = PSH_NOAPPLYNOW | PSH_PROPSHEETPAGE |
PSH_MODELESS | PSH_USECALLBACK;
pshdr.pfnCallback = PropSheetProc;
pshdr.hwndParent = mGlobalState->trayWin;
pshdr.pszCaption = L"My Settings";
pshdr.nPages = mPages.size();
pshdr.ppsp = mWinPages;
In PropSheetProc, I catch the PSCB_PRECREATE message and modify the dialog template so that it gets the DS_CENTER style:
static int CALLBACK
PropSheetProc(HWND hwndDlg, // IN
UINT uMsg, // IN
LPARAM lParam) // IN
{
// Before the dialog is created, bless it with the DS_CENTER style.
if (uMsg == PSCB_PRECREATE) {
DLGTEMPLATE *dlgTemplate = (DLGTEMPLATE *)lParam;
_ASSERT(dlgTemplate);
dlgTemplate->style |= DS_CENTER;
}
return 0;
}
However this doesn't succeed in centering the dialog. I tried to catch PSCB_INITIALIZED instead and call a CenterWindow method on the hwnd passed to the PropSheetProc:
void
CenterWindow(HWND hwndWindow) // IN
{
int nX, nY, nScreenWidth, nScreenHeight;
RECT rectWindow;
nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
GetWindowRect(hwndWindow, &rectWindow);
nX = (nScreenWidth - (rectWindow.right - rectWindow.left)) / 2;
nY = (nScreenHeight - (rectWindow.bottom - rectWindow.top)) / 2;
SetWindowPos(hwndWindow, 0, nX, nY, 0, 0,
SWP_NOZORDER | SWP_NOSIZE);
}
But that doesn't work either!
Finally, I moved the CenterWindow call to directly after the PropSheet call:
mHwnd = (HWND)PropertySheet(&pshdr);
CenterWindow(mHwnd);
return mHwnd != NULL;
And this DOES work, though on a heavily loaded system, the dialog flashes from its initial position over to its final position, which is suboptimal.
Using the PropSheetProc to modify the DLGTEMPLATE structure seems intuitive. Actually, I can apply other window styles. But DS_CENTER seems to have no effect. So what am I doing wrong? There's many ways I can work around this brokennness but why is it broken in the first place?
Overload the InitialUpdate() of the CPropertySheet, and place the CenterWindow() call there. This happens before the window is drawn on the screen, but after it is created, so it's hwnd will be valid. There is nothing broken. The dialog has to be Created to have a valid HWND. Alternatively, if your working with the resource editor you can set it's property to centered, and it will achieve the same result. Why are you overloading the WinProc for the propertysheet? The whole reason MFC uses message maps was to eliminate the need to even touch WinProc's.
If your using raw win api in a SDK style application ::
Handle WM_CREATE in the WinProc of the property sheet. The LPCREATE struct in the LPARAM will contain a valid HWND from the create call. Just make sure you pass the proper parameters back to WndProcDefault() otherwise window creation will fail.
in my application i have timer, in TimerProc i want to get handles of all windows(main and child) of the another application that has focus. I have no idea how to do that because i don't understand functions like GetNextWindow or GetParent and Z-oder of windows and i can't find anywhere very detailed explanation of how this functions works(i dont understand explanation on msdn). Please can you give me some advice or block of code which do that? Many thanks for answer.
Use GetForegroundWindow() function - it returns the HWND of the window the user currently is working with.
Then having this handle you can retrieve childs in such a way:
HWND a_hWnd = (HWND)hParent;
HWND a_FirstChild = NULL;
a_FirstChild = ::GetWindow(a_hWnd, GW_CHILD);
if (a_FirstChild != NULL)
{
HWND a_NextChild = NULL;
do
{
a_NextChild = ::GetWindow(a_FirstChild, GW_HWNDNEXT);
if (a_NextChild != NULL)
{
a_FirstChild = a_NextChild;
}
}
while (a_NextChild != NULL);
}
GetForeGroundWindow to get the current foreground window/dialog
GetParent until you get NULL (that gets you to the top level window)**
EnumChildWindows to get to all the dependent windows
** Note that an application can have more than one top level window, though this isn't usual.
Code:
void Ccpp_testDlg::DoWalk ()
{
HWND hCurrent;
HWND hNew;
hCurrent = ::GetForegroundWindow ();
hNew = hCurrent;
while (hNew != NULL)
{
hNew = ::GetParent (hCurrent);
if (hNew != NULL)
{
hCurrent = hNew;
}
}
EnumChildWindows (hCurrent, EnumProc, 0);
}
BOOL CALLBACK EnumProc (HWND hwnd,LPARAM lParam)
{
TCHAR szText [MAX_PATH];
GetWindowText (hwnd, szText, sizeof(szText));
// do something with text
return TRUE;
}