After some frustrations I have decided to investigate the behaviour of ToolTips on a ListView control. In this program, that I used in my tests I've outlined findings and conclusions.
The first conclusion is that MS, seems, has some "black holes" or "mysteries" in certain areas and is reluctant to inform about some themes. Perhaps afraid that users will know too.
This code is also zipped and can be downloaded.
The first conclusion is that MS, seems, has some "black holes" or "mysteries" in certain areas and is reluctant to inform about some themes. Perhaps afraid that users will know too.
Code:
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ' Different kinds of ToolTips on a ListView control. ' Jordi Vallès version 1a 29/04/2009 '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Original source code cames from "ListView.bas" on ..\Samples\DDT\Listview ' folder. ' ' ListView.bas example for PowerBASIC for Windows ' Copyright (c) 2009 PowerBASIC, Inc. ' All Rights Reserved. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' ' Some code added to source referenced to show and test three different kinds ' of ToolTips that a ListView control can have. ' ' The types are: ' 1) Traditional tooltips. Used with standard controls like buttons, labels, ' checkboxes, etc. ' Information found on PB forum in several sources. ' Not valid for items, subitems and headers of a ListView control. ' These tooltips can be balloon style. ' 2) Row tooltips. Can be obtained using the extended style %LVS_EX_INFOTIP ' on initialization of a ListView control causing a %LVN_GETINFOTIP ' notifications. ' Valid only for subitem 0. ' Not found information about balloon style for this tooltip type. ' Good information found on PB forum supplied by Mark Newman. ' 3) Tooltips on each one part (or column) of first or header row is some ' complicate, mainly due the poor information in MSDN about some details, ' like that header row is a control child of listview control. ' It's need to create a Hook to intercept mouse msg $WM_MOUSEMOVE when are ' over header parts. ' Independent ToolTip control is needed. ' These tooltips can be balloon style. ' Information and a good example has been found on "www.codeguru.com" web. ' ' Play with this program and see and after check the code. As you like. ' ' Notes: ' - Without the XP Manifest resource file the behaviour of header tooltip ' is some different ??? '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' - Compiled and tested with PowerBASIC for Windows 9.01 on a PC HP Pavilion ' with Windows Vista Home Premium SP1. ' - Untested on Windows XP. ' - Code posted here is released to Public Domain. Use at your own risk. '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ' SED_PBWIN #Compiler PBWin 9 #Compile Exe "LVdemo.exe" #Dim All ' Add the XP Manifest resrouce file, from \Samples\DDT\Listview folder #Resource "ListView.pbr" 'needed #Include "Win32Api.inc" #Include "Commctrl.inc" '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ %LVROWS = 50 'number of rows in the ListView %LVCOLS = 3 'number of columns in the ListView %ID_LABEL1 = 1001 'id of the label 1 control %ID_LABEL2 = 1002 'id of the label 2 control %ID_LISTVIEW = 1011 'id of the ListView control %ID_ONE = 1021 'buttons %ID_TWO = 1022 'buttons %ID_EXIT = 1023 'buttons %ID_CBOX = 1024 'check box '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Global OldLVProc As Dword 'Old Listview callback procedure pointer Global hToolTip1, hToolTip2 As Dword 'Tooltips handlers Global hLVgrid As Dword 'Listview handler Global hLVHeader As Dword 'Listview header handler (child of main listview) Global mPrevWndProc As Dword 'Previous window address Global mCurHdrItem As Long '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Function PBMain() As Long Local hDlg As Dword Local i, j As Long Local lStyle As Dword Dialog New 0, "ListView Subitem Example",,, 314, 250, %WS_VISIBLE Or %WS_CLIPSIBLINGS Or %WS_CLIPCHILDREN Or _ %WS_CAPTION Or %WS_SYSMENU Or %WS_THICKFRAME Or %WS_MINIMIZEBOX, %WS_EX_WINDOWEDGE, To hDlg Control Add Button, hDlg, %ID_ONE, "One", 165, 232, 44, 13 Control Add Button, hDlg, %ID_TWO, "Two", 211, 232, 44, 13 Control Add Button, hDlg, %ID_EXIT, "Exit", 256, 232, 44, 13 Control Add Checkbox, hDlg, %ID_CBOX, "Checkbox", 10, 234, 50, 10 Control Add Label, hDlg, %ID_LABEL1, _ "Use the Mouse, Arrow keys, PgUp, PgDn, Home, and End keys to navigate the ListView", _ 1, 1, 313, 8, %SS_CENTER Control Set Color hDlg, %ID_LABEL1, %RGB_FIREBRICK, - 1 Control Add Label, hDlg, %ID_LABEL2, _ "( Check the three ToolTips types added )", _ 1, 10, 313, 8, %SS_CENTER Control Set Color hDlg, %ID_LABEL2, %RGB_FIREBRICK, - 1 Control Add ListView, hDlg, %ID_LISTVIEW, "", 1, 22, 312, 202, _ %WS_CHILD Or %WS_VISIBLE Or %WS_TABSTOP Or %LVS_REPORT Or %LVS_SHOWSELALWAYS Or %LVS_SINGLESEL ' Add some sample text to the ListView For i = 1 To %LVCOLS ListView Insert Column hDlg, %ID_LISTVIEW, i, "Column #" + Format$(i), 100, 0 Next i For i = 1 To %LVROWS ListView Insert Item hDlg, %ID_LISTVIEW, i, 0, "Row #" + Format$(i, "000") + " Item #01" For j = 2 To %LVCOLS ListView Set Text hDlg, %ID_LISTVIEW, i, j, "Row #" + Format$(i, "000") + " Item #" + Format$(j, "00") Next j Next i '----- ToolTips support ----- for standard controls, buttons, etc. ----- hToolTip1 = CreateWindowEx(ByVal 0, "tooltips_class32", "", %TTS_NOPREFIX Or %TTS_ALWAYSTIP, _ 0, 0, 0, 0, ByVal hDlg, ByVal 0, GetModuleHandle(ByVal %NULL), ByVal 0) Dialog Send hToolTip1, %TTM_SETMAXTIPWIDTH, 0, 250 Dialog Send hToolTip1, %TTM_SETDELAYTIME, %TTDT_INITIAL, 500 Dialog Send hToolTip1, %TTM_SETDELAYTIME, %TTDT_AUTOPOP, 3000 SetToolTip1 hToolTip1, %ID_ONE, "Information about this ONE button" SetToolTip1 hToolTip1, %ID_TWO, "Explanation about TWO button" SetToolTip1 hToolTip1, %ID_EXIT, "EXIT button" + $Cr + _ "and more text" SetToolTip1 hToolTip1, %ID_CBOX, "Another additional information, in this case with a very very very long text" '----- ToolTips support ----- special for ListView header ----- hToolTip2 = CreateWindowEx(ByVal 0, "tooltips_class32", "j", %TTS_NOPREFIX Or %TTS_ALWAYSTIP, _ 0, 0, 0, 0, hLVheader, %NULL, GetModuleHandle(ByVal %NULL), ByVal 0) Dialog Send hToolTip2, %TTM_SETMAXTIPWIDTH, 0, 250 Dialog Send hToolTip2, %TTM_SETDELAYTIME, %TTDT_INITIAL, 500 Dialog Send hToolTip2, %TTM_SETDELAYTIME, %TTDT_AUTOPOP, 3000 '----- prepare a window hook ----- Control Handle hDlg, %ID_LISTVIEW To hLVgrid 'get listview handler hLVheader = GetWindow(hLVgrid, %GW_CHILD) 'get header's handler ToolTip2Hook hLVheader 'hook creation '----- set extended styles ----- lStyle = SendMessage (hLVgrid, %LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) lStyle = lStyle Or %LVS_EX_GRIDLINES Or %LVS_EX_FULLROWSELECT Or %LVS_EX_INFOTIP SendMessage hLVgrid, %LVM_SETEXTENDEDLISTVIEWSTYLE, 0, ByVal lStyle Dialog Show Modal hDlg, Call DlgProc End Function '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Sub ToolTip2Hook(ByVal hWnd As Dword) mPrevWndProc = SetWindowLong(hWnd, %GWL_WNDPROC, CodePtr(WindowProc)) mCurHdrItem = -1 End Sub '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Sub ToolTip2UnHook(ByVal hWnd As Dword) If mPrevWndProc Then SetWindowLong hWnd, %GWL_WNDPROC, mPrevWndProc End Sub '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ CallBack Function DlgProc Static zText As Asciiz * 256 Select Case As Long Cb.Msg Case %WM_INITDIALOG 'Subclass the listview control so we can receive %WM_LButtonDown and %WM_KeyDown messages OldLVProc = SetWindowLong(GetDlgItem(Cb.Hndl, %ID_LISTVIEW), %GWL_WNDPROC, ByVal CodePtr(LVProc)) Case %WM_NOTIFY Local nmlv As NMLISTVIEW Ptr Local pTt As NMLVGETINFOTIP Ptr nmlv = Cb.Lparam If @nmlv.hdr.hWndFrom = hLVgrid Then 'Is the Notify from the ListView? Select Case @nmlv.hdr.code Case %LVN_GETINFOTIP 'Tooltip is requesting text pTt = Cb.Lparam ListView_GetItemText hLVgrid, @pTt.iItem, 0, zText, SizeOf(zText) zText = "Tip: Row = " + Str$(@pTt.iItem) + ", Col = " + Str$(@pTt.iSubitem) + _ $Cr + "Text: " + zText @pTt.cchTextMax = SizeOf(zText) @pTt.pszText = VarPtr(zText) Function = 1 Exit Function End Select End If Case %WM_COMMAND Select Case Cb.Ctl Case %ID_EXIT, %IDCANCEL If Cb.CtlMsg = %BN_CLICKED Then If MsgBox("Abort the process?", %MB_YESNO Or %MB_ICONQUESTION Or %MB_TASKMODAL, _ "Abort?") = %IDYES Then Dialog End Cb.Hndl, 0 End If Case %ID_ONE ' ... Case %ID_TWO ' ... Case %ID_CBOX ' ... End Select Case %WM_NCACTIVATE Static hWndSaveFocus As Dword If IsFalse Cb.WParam Then hWndSaveFocus = GetFocus() ElseIf hWndSaveFocus Then SetFocus(hWndSaveFocus) hWndSaveFocus = 0 End If Case %WM_SYSCOMMAND If Cb.WParam = %SC_CLOSE Then If MsgBox("Are you sure you want to quit the application?", _ %MB_YESNO Or %MB_DEFBUTTON2 Or %MB_SYSTEMMODAL) = %IDNO Then Function = 1 : Exit Function End If End If Case %WM_DESTROY If OldLVProc Then SetWindowLong(GetDlgItem(Cb.Hndl, %ID_LISTVIEW), %GWL_WNDPROC, OldLVProc) ToolTip2UnHook hLVheader End Select End Function ' ListView callback procedure '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Function LVProc(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Static Col As Long ' Selected column Static Row As Long ' Selected row Local c As Long ' Number of rows per page Local LVHT As LVHITTESTINFO ' Contains information about a mouse clik on the ListView Select Case As Long wMsg Case %WM_LBUTTONDOWN lvht.pt.x = Lo(Word, lparam) ' X coordinate of mouse left button down lvht.pt.y = Hi(Word, lparam) ' Y coordinate of mouse left button down ' Find the listview item and subitem at these X, Y coordinates SendMessage(hwnd, %LVM_SUBITEMHITTEST, ByVal 0, ByVal VarPtr(LVHT)) ' Did we find a listview item at these coordinates? If lVHT.iItem <> -1 Then ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, lVHT.iItem + 1, LVHT.iSubItem + 1) End If ' We handled this message, so we need to return a zero ' and not call the the original listview callback. Function = 0 Exit Function Case %WM_KEYDOWN Select Case As Long wParam Case %VK_UP ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, Row - 1, Col) Case %VK_DOWN ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, Row + 1, Col) Case %VK_RIGHT ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, Row, Col + 1) Case %VK_LEFT ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, Row, Col - 1) Case %VK_PGUP ' Get the number of rows per page in the ListView c = SendMessage(hWnd, %LVM_GETCOUNTPERPAGE, 0, 0) ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, Row - c, Col) Case %VK_PGDN ' Get the number of rows per page in the ListView c = SendMessage(hWnd, %LVM_GETCOUNTPERPAGE, 0, 0) ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, Row + c, Col) Case %VK_HOME ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, 1, 1) Case %VK_END ' Update the ListView with the new selection UpdateLVSelect(GetParent(hWnd), Row, Col, %LVROWS, 1) End Select ' We handled this message, so we need to return a zero ' and not call the the original listview callback. Function = 0 Exit Function End Select ' if we did not handle this message, pass the message on to the ' original callback for the listview control. Function = CallWindowProc(OldLVProc, hWnd, wMsg, wParam, lParam) End Function ' Unselect the previous listview selection, select the new item and then ensure the new ' item is visible. '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Sub UpdateLVSelect(ByVal hDlg As Long, ByRef Row As Long, ByRef Col As Long, ByVal NewRow As Long, ByVal NewCol As Long) ' Make sure the new row is within range If NewRow < 1 Then NewRow = 1 ElseIf NewRow > %LVROWS Then NewRow = %LVROWS End If ' Make sure the new column is within range If NewCol < 1 Then NewCol = 1 ElseIf NewCol > %LVCOLS Then NewCol = %LVCOLS End If ' If the previous and new selection are the same then do nothing If (Row = NewRow) And (Col = NewCol) Then Exit Sub ' Unselect the previous selection, required when seleting subitems ' even if the ListView contains the %LVS_SINGLESEL style ListView Unselect hDlg, %ID_LISTVIEW, Row, Col ' Update the Row and Column variables Row = NewRow Col = NewCol ' Select the new ListView item ListView Select hDlg, %ID_LISTVIEW, Row, Col ' Ensure the new ListView item is visible ListView Visible hDlg, %ID_LISTVIEW, Row End Sub '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Function WindowProc(ByVal hWnd As Dword, ByVal wMsg As Long, ByVal wParam As Dword, ByVal lParam As Long) As Long Local hti As HD_HITTESTINFO Local zText As Asciiz * 256 Select Case wMsg Case %WM_MOUSEMOVE GetCursorPos hti.pt 'get mouse coordinates on screen MapWindowPoints 0, hLVheader, hti.pt, 1 'map them to header SendMessage hLVheader, %HDM_HITTEST, 0, VarPtr(hti) 'see what column we are over If hti.iItem <> mCurHdrItem Then mCurHdrItem = hti.iItem DelToolTip2 hWnd If mCurHdrItem <> -1 Then '-1 is over listview itself. zText = GetToolTipText(hti.iItem) SetToolTip2 hWnd, zText End If End If End Select Function = CallWindowProc(mPrevWndProc, hWnd, wMsg, wParam, lParam) End Function '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Function GetToolTipText(ByVal Id As Long) As String Select Case As Long Id Case 0 : Function = " This is the first column" Case 1 : Function = " This is the second column" + $Cr + _ " that follows the first column" Case 2 : Function = " This is the third column" + $Cr + _ " that follows the second column" + $Cr + _ " and first column too" End Select End Function '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Sub SetToolTip1(hWndTip As Dword, id As Long, Byval txt As String) 'for standard controls Local hLocalWnd As Dword Local zToolTipText As Asciiz * 256 Local ti As TOOLINFO zToolTipText = txt hLocalWnd = GetParent(hWndTip) ti.cbSize = SizeOf(ti) ti.uFlags = %TTF_SUBCLASS Or %TTF_IDISHWND ti.hWnd = hLocalWnd ti.uId = GetDlgItem(hLocalWnd, id) ti.lpszText = VarPtr(zToolTipText) SendMessage hToolTip1, %TTM_ADDTOOL, 0, VarPtr(ti) End Sub '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Sub SetToolTip2(hWnd As Dword, Byval txt As String) 'for ListView headers Local hLocalWnd As Dword Local zToolTipText As Asciiz * 256 Local ti As TOOLINFO zToolTipText = txt hLocalWnd = hWnd ti.cbSize = SizeOf(ti) ti.uFlags = %TTF_SUBCLASS Or %TTF_IDISHWND ti.hWnd = hLocalWnd ti.uId = hLocalWnd ti.lpszText = VarPtr(zToolTipText) SendMessage hToolTip2, %TTM_ADDTOOL, 0, VarPtr(ti) End Sub '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ Sub DelToolTip2(hWnd As Dword) 'for ListView headers Local ti As TOOLINFO ti.cbSize = SizeOf(ti) ti.hWnd = hWnd While SendMessage(hToolTip2, %TTM_ENUMTOOLS, 0, VarPtr(ti)) SendMessage hToolTip2, %TTM_DELTOOL, 0, VarPtr(ti) Wend End Sub '¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ 'eof
Comment