Right now, I have a tool tip that pops up when I hover over an edit box. The problem is that this tool tip contains multiple error messages and they are all in one long line. I need to have each error message be on its own line. The error messages are contained in a CString with a new line seperating them.
My existing code is below.
BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
// need to handle both ANSI and UNICODE versions of the message
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
// TCHAR szFullText[256];
CString strTipText=_T("");
UINT nID = pNMHDR->idFrom;
if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
}
//m_errProjAccel[ch] contains 1 or more error messages each seperated by a new line.
if((int)nID >= ID_PROJECTED_ACCEL1 && (int)nID < ID_PROJECTED_ACCEL1 + PROJECTED_ROWS -1 ) {
int ch = nID - ID_PROJECTED_ACCEL1;
strTipText = m_errProjAccel[ch];
}
#ifndef _UNICODE
if (pNMHDR->code == TTN_NEEDTEXTA)
lstrcpyn(pTTTA->szText, strTipText, sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0]));
else
_mbstowcsz(pTTTW->szText, strTipText, sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0]));
#else
if (pNMHDR->code == TTN_NEEDTEXTA)
_wcstombsz(pTTTA->szText, strTipText, sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0]));
else
lstrcpyn(pTTTW->szText, strTipText, sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0]));
#endif
*pResult = 0;
// bring the tooltip window above other popup windows
::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
return TRUE; // message was handled
}
Creating multiline tooltips is explained here in the MSDN library - read the "Implementing Multiline ToolTips" section. You should send a TTM_SETMAXTIPWIDTH message to the ToolTip control in response to a TTN_GETDISPINFO notification to force it to use multiple lines. In your string you should separate lines with \r\n.
Also, if your text is more than 80 characters, you should use the lpszText member of the NMTTDISPINFO structure instead of copying into the szText array.
Related
I am trying to remove windows error sound from my Flutter (Win32) Application. After some research I came up with this fix. I tried this fix but it's not helping in my Flutter application.
Heres the code to handle WM_SYSCHAR message:
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_SYSCHAR) {
std::cout << "SYSCHAR from win32" << std::endl;
return 0;
}
...
}
When I press the Alt+Space, "SYSCHAR from win32" is printed in the console. But whenever I press any other key with Alt, this is not printed and the Windows error sound is played. It seems like SYSCHAR message is handled somewhere else?
This can be used to know the working and initialization of Win32 App in Flutter.
I just want to tell the Application that Alt+Key combinations are handled and it doesn't have to play Windows error sound.
Thanks to #IInspectable for suggesting me to use keyboard accelerators.
The problem is Flutter's main loop doesn't have keyboard accelerators. So I followed how to use keyboard accelerators documentation and modified the main loop as follows:
Created accelerator table by calling CreateAcceleratorTable
LPACCEL accels = GenerateAccels();
HACCEL haccel = CreateAcceleratorTable(accels, 36);
if (haccel==NULL) {
return EXIT_FAILURE;
}
Here's the GenerateAccels function:
LPACCEL GenerateAccels() {
LPACCEL lpAccel = new ACCEL[36];
// Alt + Number combinations:
for (int i = 0; i < 10; i++) {
lpAccel[i].fVirt = FALT;
lpAccel[i].key = (WORD)(0x30 + i);
}
// Alt + Alphabet combinations (NOT WORKING AT THE MOMENT):
for (int i = 0; i < 26; i++) {
lpAccel[i + 10].fVirt = FALT;
lpAccel[i + 10].key = (WORD)(0x41 + i);
}
return lpAccel;
}
Then adding a call to TranslateAccelerator in the main loop
::MSG msg = { };
while (::GetMessage(&msg, nullptr, 0, 0) > 0) {
if (!TranslateAccelerator(msg.hwnd, haccel, &msg)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
I also added this check to prevent error sound from playing when any key is pressed after pressing Alt (Alt is not held down).
flutter_window.cpp
case WM_SYSCOMMAND:
// If the selection is in menu
// handle the key event
// This prevents the error/beep sound
if (wparam == SC_KEYMENU) {
return 0;
}
Note: One thing that isn't working is Alt + Alphabet combinations. When pressed, it is still playing that error sound. In my case it's not important right now, but if someone finds the fix then please share.
I've got a project on vs2008 without unicode support and there is no tooltip text showing. I've tried the same code on another project with unicode support and it works alright. What am I doing wrong?
BOOL CListCtrl_ToolTip::OnToolNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
CPoint pt(GetMessagePos());
ScreenToClient(&pt);
int nRow, nCol;
CellHitTest(pt, nRow, nCol);
CString tooltip = GetToolTipText(nRow, nCol);
//MessageBox(tooltip,NULL, MB_OK);
if (tooltip.IsEmpty())
return FALSE;
// Non-unicode applications can receive requests for tooltip-text in unicode
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
#ifndef _UNICODE
if (pNMHDR->code == TTN_NEEDTEXTA)
lstrcpyn(pTTTA->szText, static_cast<LPCTSTR>(tooltip), sizeof(pTTTA->szText));
else
#else
if (pNMHDR->code == TTN_NEEDTEXTA)
_wcstombsz(pTTTA->szText, static_cast<LPCTSTR>(tooltip), sizeof(pTTTA->szText));
else
lstrcpyn(pTTTW->szText, static_cast<LPCTSTR>(tooltip), sizeof(pTTTW->szText)/sizeof(WCHAR));
#endif
// If wanting to display a tooltip which is longer than 80 characters,
// then one must allocate the needed text-buffer instead of using szText,
// and point the TOOLTIPTEXT::lpszText to this text-buffer.
// When doing this, then one is required to release this text-buffer again
return TRUE;
}
The tooltip string is filled with the needed value but the text doesn't show up. The problem occurs when the pTTW->szText is assigned. I've tried to assign address of my string to lpszText, but the tooltip showed chinese symbols or something.
Probably the listview control is always getting unicode messages for TTN_NEEDTEXT, and it doesn't matter if the project is unicode or ANSI. Therefore you cannot rely on #define UNICODE
Related issue: TTN_NEEDTEXTA/TTN_NEEDTEXTW
This should work for both unicode and non-unicode:
BEGIN_MESSAGE_MAP(TList, CListCtrl)
ON_NOTIFY_EX(TTN_NEEDTEXTA, 0, OnToolNeedText)
ON_NOTIFY_EX(TTN_NEEDTEXTW, 0, OnToolNeedText)
END_MESSAGE_MAP()
BOOL TList::OnToolNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
CPoint pt(GetMessagePos());
ScreenToClient(&pt);
int nRow, nCol;
CellHitTest(pt, nRow, nCol);
CString tooltip = GetToolTipText(nRow, nCol);
if (tooltip.IsEmpty())
return FALSE;
if (pNMHDR->code == TTN_NEEDTEXTW)
{
TOOLTIPTEXTW* ttext = (TOOLTIPTEXTW*)pNMHDR;
CStringW sw(tooltip);
lstrcpynW(ttext->szText, sw, sizeof(ttext->szText)/sizeof(wchar_t));
}
else
{
TOOLTIPTEXTA* ttext = (TOOLTIPTEXTA*)pNMHDR;
CStringA sa(tooltip);
lstrcpynA(ttext->szText, sa, sizeof(ttext->szText));
}
return TRUE;
}
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.
I have these links with code:
WMMouseWheel not working in Delphi
How to disable MouseWheel if mouse is not over VirtualTreeView (TVirtualStringTree)
Translated it to C++ Builder but it doesn't work:
UPDATE: After narrowing the problem down it appears that WM_MOUSEWHEEL messages don't work over unfocused TVirtualStringTree control only, they work over other controls. When focus is on e.g. TMemo control, other TMemo control scrolls on wheel but not TVirtualStringTree control. When focus is on TVirtualStringTree it scrolls TVirtualStringTree but not other controls. So the problem is now specific to TVirtualStringTree only.
void __fastcall TForm1::ApplicationEventsMessage(tagMSG &Msg, bool &Handled)
{
TPoint Pt;
HWND Wnd;
if (Msg.message == WM_MOUSEWHEEL ||
Msg.message == WM_VSCROLL ||
Msg.message == WM_HSCROLL)
{
if (GetCursorPos(&Pt))
{
Wnd = WindowFromPoint(Pt);
// It must be a VCL control otherwise we could get access violations
if (IsWindowEnabled(Wnd) && FindControl(Wnd) != NULL)
{
Msg.hwnd = Wnd; // change the message receiver to the control under the cursor
}
}
}
}
Different version of the similar code, also doesn't work:
TPoint pnt;
TWinControl *ctrl;
if ((Msg.message == WM_MOUSEWHEEL ||
Msg.message == WM_VSCROLL ||
Msg.message == WM_HSCROLL) &&
GetCursorPos(&pnt))
{
ctrl = FindVCLWindow(pnt);
if (ctrl != NULL)
{
SendMessage(ctrl->Handle, Msg.message, Msg.wParam, Msg.lParam); // No effect
// SendMessage(ctrl->Handle, WM_VSCROLL, 1, 0); // This is the only thing that actually moves scrollbars but this is not exactly the same message like above
// Msg.hwnd = ctrl->Handle; // No effect
this->Caption=ctrl->Name; // This shows correct control name so the message IS GETTING THROUGH!
Handled = true;
}
}
It should work but it doesn't. Tried other code as well. No effect - mouse wheel does not operate on unfocused control. As you can see, I checked for all 3 variants of wheel messages, it gets correct control under the mouse, it shows that control name but the control doesn't receive wheel messages.
Any ideas what piece of the puzzle am I missing to get it to work?
As nobody offered any proper solution, I am posting my own. The solution is not perfect but at least it does what it needs to do - mouse wheel scrolls all controls under it, including the VirtualTreeView controls. The code in solution is in C++ but Delphi version is very similar (it only needs to be translated).
My current solution is to grab WM_MOUSEWHEEL events and translate them into WM_VSCROLL or WM_HSCROLL to which VirtualTreeView reacts and scrolls the content. Additionally, it needs to take into account high-precision mouse wheels which can have smaller value than WHEEL_DELTA (which is set to 120). Finally, it needs to take into account user setting for number of lines to scroll (set in Control Panel in Windows). So here goes:
Put a TApplicationEvents to a form and in the OnMessage event do this:
void __fastcall TFormMain::ApplicationEventsMessage(tagMSG &Msg, bool &Handled)
{
// Check these 3 messages because some mouse drivers may use VSCROLL instead of MOUSESWHEEL message
if (Msg.message == WM_MOUSEWHEEL || Msg.message == WM_VSCROLL || Msg.message == WM_HSCROLL)
{
TPoint pnt;
TWinControl *ctrl;
if (!GetCursorPos(&pnt)) return;
ctrl = FindVCLWindow(pnt);
if (ctrl != NULL)
{
// ToDo: implement if user needs wheel-click - then we also need KEYSTATE but for this example it is not needed
// int fwKeys = GET_KEYSTATE_WPARAM(Msg.wParam);
int zDelta = GET_WHEEL_DELTA_WPARAM(Msg.wParam),
pvParam = 3; // Windows default value
unsigned MyMsg = WM_VSCROLL;
// ToDo: extract SystemParametersInfo somewhere else so it is not extracted for each WM_MOUSEWHEEL message which may not be needed
switch (Msg.message)
{
// This will translate WM_MOUSEWHEEL into WM_VSCROLL
case WM_MOUSEWHEEL:
case WM_VSCROLL:
// Windows setting which determines how many lines to scroll - we'll send that many WM_VSCROLL or WM_HSCROLL messages
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &pvParam, 0);
MyMsg = WM_VSCROLL;
break;
case WM_HSCROLL:
// Same as above but for WM_HSCROLL (horizontal wheel)
SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &pvParam, 0);
MyMsg = WM_HSCROLL;
break;
}
// This calculation takes into account high-precision wheels with delta smaller than 120
// Possible TODO: Round up values smaller than 1 (e.g. 0.75 * pvParam) if pvParam is 1
int ScrollBy = ((double)zDelta / (double)WHEEL_DELTA) * pvParam;
// Send multiple messages based on how much the zDelta value was
if (zDelta > 0)
{
do
{
SendMessage(ctrl->Handle, MyMsg, SB_LINEUP, 0);
}
while (--ScrollBy > 0);
}
else
{
do
{
SendMessage(ctrl->Handle, MyMsg, SB_LINEDOWN, 0);
}
while (++ScrollBy < 0);
}
Handled = true;
}
}
}
I recently have been adding tooltips to each dialog item in my application. Prior to adding each to a string resource I wanted to do it in hardcoded text so I can change it easily as I am writing them. Now it has come time to pull strings from the resource files and it seems that I cannot get one to come out and display as a tooltip.
The code below produces a blank tooltip. Though if I replace tmpStr with a real string such as [_T("Tool Tip Text")] it works fine.
Code:
BOOL CCustomDialog::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
UINT nID = pNMHDR->idFrom;
if (pTTT->uFlags & TTF_IDISHWND)
{
nID = ::GetDlgCtrlID((HWND)nID);
}
if(nID)
{
CString tmpStr;
if( nID == IDC_BUTTON1)
{
GetDlgItemText(IDS_BUTTON1_TT, tmpStr);
_tcsncpy_s(pTTT->szText, tmpStr, _TRUNCATE);
}
*pResult = 0;
return TRUE;
}
return FALSE;
}
What could be the cause of this?
EDIT: If I put the controls ID of the control I wish to display a tooltip on it, it works and shows the controls description as the text. If I add a String resource in the resource file that the control is located in, the string resource will not come out as a tooltip.
So it seems that this is only a problem with the String resources?
In the following section of code
if( nID == IDC_BUTTON1)
{
GetDlgItemText(IDS_BUTTON1_TT, tmpStr);
_tcsncpy_s(pTTT->szText, tmpStr, _TRUNCATE);
}
It looks like you have a button with an ID of IDC_BUTTON1 with an associated text string in your resource file with ID of IDS_BUTTON1_TT.
If that's true, then you need to use tmpStr.LoadString(IDS_BUTTON1_TT) to get the text. Don't use GetDlgItemText() unless you want the text of the button control, then you need to use it's ID of IDC_BUTTON1 instead. So, do it like this
if( nID == IDC_BUTTON1)
{
tmpStr.LoadString(IDS_BUTTON1_TT);
_tcsncpy_s(pTTT->szText, tmpStr, _TRUNCATE);
}