
Code: Select all
;- Notes
; - Coded by Xombie 9/8/2005
; - Tooltip code provided by Sparkie and Andre (http://forums.purebasic.com/english/viewtopic.php?t=14482&highlight=changetooltip)
; with a *very* slight modification by me.
; - Right-click header detection based on code found at (http://groups.google.com/group/microsoft.public.vb.controls/browse_frm/thread/63486daff309aab0/e1ad74ef5b0575b8?lnk=st&q=Displaying+pop-up+menu+on+right-click+ListView&rnum=1&hl=en#e1ad74ef5b0575b8)
;
;- Constants
#LVM_GETHEADER = 4127
;- Structures
Structure HDHITTESTINFO
pt.POINT
flags.l
iItem.l
EndStructure
Enumeration ; Window IDs
#WindowMain
EndEnumeration
Enumeration ; Menu IDs
#MenuPrimary
#MenuPrimaryTest
#MenuSecondary
#MenuSecondaryTest
EndEnumeration
Enumeration ; Gadget IDs
#ListPrimary
#ListSecondary
#ButtonClose
EndEnumeration
;- Global Variables
Global TooltipPrimaryRow.l
; Current row containing the tooltip text for our primary listview control.
Global TooltipPrimary.l
; Pointer to the tooltip used in the primary listview control.
Global TooltipSecondaryRow.l
Global TooltipSecondary.l
; Ditto for the secondary listview control.
;/
; I use global variables for these but if you're maintaining a lot of listview controls, you may want to think about
; some sort of structure of something. Otherwise, it'd get ugly quickly. Also, you may have a better idea than me
; about how to store these. I'm just doing this for my example.
;/
;- Tooltip Functions
Procedure.l AddTooltip(Gadget, Tooltext$, maxW)
;--> Remove the #TTS_BALLOON flag in the next line if you want the rectangular Tooltip
hToolTip = CreateWindowEx_(0, "ToolTips_Class32", "", #TTS_NOPREFIX | #TTS_BALLOON, 0, 0, 0, 0, 0, 0, GetModuleHandle_(0), 0)
SendMessage_(hToolTip, #TTM_SETTIPTEXTCOLOR, GetSysColor_(#COLOR_INFOTEXT), 0)
SendMessage_(hToolTip, #TTM_SETTIPBKCOLOR, GetSysColor_(#COLOR_INFOBK), 0)
ttAdd.TOOLINFO\cbSize = SizeOf(TOOLINFO)
ttAdd\uFlags = #TTF_SUBCLASS | #TTF_IDISHWND
;--> Here's where the multiline comes into play by setting the maxWidth
SendMessage_(hToolTip, #TTM_SETMAXTIPWIDTH, 0, maxW)
ttAdd\hWnd = WindowID()
ttAdd\uId = GadgetID(Gadget)
ttAdd\hinst = 0
ttAdd\lpszText = @Tooltext$
SendMessage_(hToolTip, #TTM_ADDTOOL, 0, ttAdd)
SendMessage_(hToolTip, #TTM_SETDELAYTIME, #TTDT_AUTOPOP, 15000)
;/
; SendMessage_(hToolTip, #TTM_UPDATE , 0, 0)
;/
; The #TTM_UPDATE forces a redraw of the tooltip - causes it to popup immediate upon creation. Disabled here so there is some
; delay in the initial time required to 'popup'.
ProcedureReturn hToolTip
EndProcedure
Procedure.l RemoveToolTip(hTT.l, Gadget.l)
ttRemove.TOOLINFO\cbSize = SizeOf(TOOLINFO)
ttRemove\hWnd = WindowID()
ttRemove\uId = GadgetID(Gadget)
SendMessage_(hTT, #TTM_DELTOOL, 0, ttRemove)
EndProcedure
Procedure.l ChangeToolTip(hTT.l, Gadget.l, Tooltext$)
ttChange.TOOLINFO\cbSize = SizeOf(TOOLINFO)
ttChange\hWnd = WindowID()
ttChange\uId = GadgetID(Gadget)
ttChange\lpszText = @Tooltext$
SendMessage_(hTT, #TTM_UPDATETIPTEXT, 0, ttChange)
EndProcedure
;- Callback
Procedure.l WindowCallback(HandleWindow, Message, wParam, lParam)
; Main form callback procedure.
HandleHeader.l
; Store the handle for the listview control's header.
*NotifyHeader.NMHDR
; Notification message header.
TestHit.HDHITTESTINFO
; Store information about header hit testing.
IndexColumn.l
; Store the column index from our header.
Protected iLoop.l
;
Protected lResult.l
;
lResult = #PB_ProcessPureBasicEvents
;
If Message = #WM_SETCURSOR
; Event caused when the cursor moves within a window and mouse input is not captured.
If wParam = GadgetID(#ListPrimary)
; wParam returns the handle of the window that generates the event. Check it against our listview control.
GetCursorPos_(TestHit\pt)
; Retrieve the cursor position.
ScreenToClient_(wParam, TestHit\pt)
; Translate our coordinates to screen coordinates.
SendMessage_(wParam, #LVM_HITTEST, 0, TestHit)
; Retrieve the listview item index. The call uses the coordinate information we retrieved previously.
If TestHit\iItem = 0
; Over the first row.
If TooltipPrimary
; Tooltip already exists. Either the tooltip exists for the first row or a different row.
If TooltipPrimaryRow <> TestHit\iItem
; Tooltip was set to a previous row's text.
RemoveToolTip(TooltipPrimary, #ListPrimary)
; Destroy the previous tooltip.
TooltipPrimary = AddTooltip(#ListPrimary, "First Row", 200)
; Add the new tooltip. Notice we don't use the 'changetooltip' function. By destroying and then adding
; the tooltip again, we assure that the previous tooltip will go away and the new tooltip will popup after a delay.
EndIf
;
Else
; Tooltip does not exist.
TooltipPrimary = AddTooltip(#ListPrimary, "First Row", 200)
; Add the new tooltip.
EndIf
;
ElseIf TestHit\iItem = 1
; Hovering over the second line.
If TooltipPrimary
; Tooltip already exists. Either the tooltip exists for the second row or a different row.
If TooltipPrimaryRow <> TestHit\iItem
; Tooltip was set to a previous row's text.
RemoveToolTip(TooltipPrimary, #ListPrimary)
; Destroy the previous tooltip.
TooltipPrimary = AddTooltip(#ListPrimary, "Second Row", 200)
; Add the new tooltip. Notice we don't use the 'changetooltip' function. By destroying and then adding
; the tooltip again, we assure that the previous tooltip will go away and the new tooltip will popup after a delay.
EndIf
;
Else
; Tooltip does not exist.
TooltipPrimary = AddTooltip(#ListPrimary, "Second Row", 200)
; Add the new tooltip.
EndIf
;
ElseIf TestHit\iItem = 3
; Hovering over the fourth line. Notice we skipped the third line on purpose.
If TooltipPrimary
; Tooltip already exists. Either the tooltip exists for the fourth row or a different row.
If TooltipPrimaryRow <> TestHit\iItem
; Tooltip was set to a previous row's text.
RemoveToolTip(TooltipPrimary, #ListPrimary)
; Destroy the previous tooltip.
TooltipPrimary = AddTooltip(#ListPrimary, "Really super duper long row text so we can check out the wrapping function so nicely provided by Sparkie and Andre. By the way, this is for the fourth row. We skipped the third row entirely. Hello from Xombie ^_^", 200)
; Add the new tooltip. Notice we don't use the 'changetooltip' function. By destroying and then adding
; the tooltip again, we assure that the previous tooltip will go away and the new tooltip will popup after a delay.
EndIf
;
Else
; Tooltip does not exist.
TooltipPrimary = AddTooltip(#ListPrimary, "Really super duper long row text so we can check out the wrapping function so nicely provided by Sparkie and Andre. By the way, this is for the fourth row. We skipped the third row entirely. Hello from Xombie ^_^", 200)
; Add the new tooltip.
EndIf
;
Else
; At this point, we should've already checked lines that we *want* to have tooltips. So now we're either not
; hovering over a listview line or we're hovering over a line that has no tooltip. Remove the current tooltip.
If TooltipPrimary : TooltipPrimary = RemoveToolTip(TooltipPrimary, #ListPrimary) : TooltipPrimary = 0 : EndIf
; Remove the previous tooltip and set our global variable to 0 so we know to recreate it later.
EndIf
;
TooltipPrimaryRow = TestHit\iItem
; Update our global row value so we know whether to destroy or change the current primary listview item text.
ElseIf wParam = GadgetID(#ListSecondary)
; wParam returns the handle of the window that generates the event. Check it against our listview control.
GetCursorPos_(TestHit\pt)
; Retrieve the cursor position.
ScreenToClient_(wParam, TestHit\pt)
; Translate our coordinates to screen coordinates.
SendMessage_(wParam, #LVM_HITTEST, 0, TestHit)
; Retrieve the listview item index. The call uses the coordinate information we retrieved previously.
If TestHit\iItem = 2
; Over the third row.
If TooltipSecondary
; Tooltip already exists. Either the tooltip exists for the first row or a different row.
If TooltipSecondaryRow <> TestHit\iItem
; Tooltip was set to a previous row's text.
RemoveToolTip(TooltipSecondary, #ListSecondary)
; Destroy the previous tooltip.
TooltipSecondary = AddTooltip(#ListSecondary, "Congratulations! You've found the hidden tooltip in the secondary listview control! You win NO prize! :D", 200)
; Add the new tooltip. Notice we don't use the 'changetooltip' function. By destroying and then adding
; the tooltip again, we assure that the previous tooltip will go away and the new tooltip will popup after a delay.
EndIf
;
Else
; Tooltip does not exist.
TooltipSecondary = AddTooltip(#ListSecondary, "Congratulations! You've found the hidden tooltip in the secondary listview control! You win NO prize! :D", 200)
; Add the new tooltip.
EndIf
;
Else
; At this point, we should've already checked lines that we *want* to have tooltips. So now we're either not
; hovering over a listview line or we're hovering over a line that has no tooltip. Remove the current tooltip.
If TooltipSecondary : TooltipSecondary = RemoveToolTip(TooltipSecondary, #ListSecondary) : TooltipSecondary = 0 : EndIf
; Remove the previous tooltip and set our global variable to 0 so we know to recreate it later.
EndIf
;
TooltipSecondaryRow = TestHit\iItem
; Update our global row value so we know whether to destroy or change the current secondary listview item text.
EndIf
;
ElseIf Message = #WM_NOTIFY
; Received a notify event. Usually passed to the parent container for a control.
*NotifyHeader = lParam
; Receive the message header information passed by the notify event.
If *NotifyHeader\code = #NM_RCLICK
; Right-click event was received.
GetCursorPos_(TestHit\pt)
; Retrieve the cursor position.
ScreenToClient_(*NotifyHeader\hwndFrom, TestHit\pt)
; Translate our coordinates to screen coordinates.
IndexColumn = SendMessage_(*NotifyHeader\hwndFrom, #HDM_HITTEST, 0, TestHit)
; Retrieve the column index from our header control. The call uses the coordinate information we retrieved previously.
If *NotifyHeader\hwndFrom = SendMessage_(GadgetID(#ListPrimary), #LVM_GETHEADER, 0, 0)
; Test if the header clicked is from the 'primary' listview control. #LVM_GETHEADER returns the handle to the header
; control used in the listview control. This is the same handle used as part of the NMHDR (*messageheader) variable.
If IndexColumn = 0
; Right-clicked on the first column.
HoldNumber.s = InputRequester("Number Search", "Please enter a number to search for (eg., '1', '2', '3' or '4')", "")
; For our test, prompt the user for a number used in the primary listview control. We'll search for it.
For iLoop = 0 To CountGadgetItems(#ListPrimary) - 1
; Loop through the number of items in our primary listview control.
If HoldNumber = GetGadgetItemText(#ListPrimary, iLoop, 0)
; Check if the entered number is the same as the one in the listview.
SetGadgetItemState(#ListPrimary, iLoop, #PB_ListIcon_Selected)
; Select the item.
SendMessage_(GadgetID(#ListPrimary), #LVM_ENSUREVISIBLE, iLoop, #False)
; Make sure the newly selected item is visible. Not useful in this example but only if we have many more items.
Break
; We found the item. Break from our loop.
EndIf
;
Next iLoop
;
ElseIf IndexColumn = 1
; User right-clicked on the second column.
DisplayPopupMenu(#MenuPrimary, WindowID())
; Display a popup menu.
EndIf
;
ElseIf *NotifyHeader\hwndFrom = SendMessage_(GadgetID(#ListSecondary), #LVM_GETHEADER, 0, 0)
;
If IndexColumn = 0
; Right-clicked on the first column.
DisplayPopupMenu(#MenuSecondary, WindowID())
; Display a popup menu.
ElseIf IndexColumn = 2
; Right-clicked on the first column.
HoldString.s = InputRequester("Text Search", "Please enter text to search in the 3rd column (eg. 'fo').", "")
; For our test, prompt the user for a number used in the primary listview control. We'll search for it.
For iLoop = 0 To CountGadgetItems(#ListSecondary) - 1
; Loop through the number of items in our primary listview control.
If FindString(LCase(GetGadgetItemText(#ListSecondary, iLoop, 2)), LCase(HoldString), 1)
; Check if the 3rd column contains the text we search for.
SetGadgetItemState(#ListSecondary, iLoop, #PB_ListIcon_Selected)
; Select the item.
SendMessage_(GadgetID(#ListSecondary), #LVM_ENSUREVISIBLE, iLoop, #False)
; Make sure the newly selected item is visible. Not useful in this example but only if we have many more items.
Break
; We found the item. Break from our loop.
EndIf
;
Next iLoop
;
EndIf
;
EndIf
;
EndIf
;
EndIf
;
ProcedureReturn lResult
;
EndProcedure
;- Main Program Start
EventID.l
; Variable to hold the window message.
DoQuit.b
; Variable to control whether we quit the window or not. Automatically set to #False.
If OpenWindow(#WindowMain, 0, 0, 400, 120, #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered | #PB_Window_SystemMenu | #PB_Window_TitleBar, "Listview Header Test")
; Create the main window.
If CreateGadgetList(WindowID())
;
AdvancedGadgetEvents(#True)
; Enable advanced gadget events.
ListIconGadget(#ListPrimary, 0, 0, 200, 100, "First", 50, #PB_ListIcon_AlwaysShowSelection | #PB_ListIcon_FullRowSelect | #PB_ListIcon_GridLines)
; Create our primary listview control.
ListIconGadget(#ListSecondary, GadgetX(#ListPrimary) + GadgetWidth(#ListPrimary) + 1, GadgetY(#ListPrimary), 200, 100, "First", 50, #PB_ListIcon_AlwaysShowSelection | #PB_ListIcon_FullRowSelect | #PB_ListIcon_GridLines)
; Create our secondary listview control.
ButtonGadget(#ButtonClose, GadgetX(#ListSecondary) + GadgetWidth(#ListSecondary) - 100, GadgetY(#ListSecondary) + GadgetHeight(#ListSecondary) + 1, 100, 20, "Close")
; Create our close button.
EndIf
;
SetWindowCallback(@WindowCallback())
; Set the main window callback.
AddGadgetColumn(#ListPrimary, 1, "Second", 50)
AddGadgetColumn(#ListPrimary, 2, "Third", 50)
; Add some dummy columns to our primary listicon.
AddGadgetColumn(#ListSecondary, 1, "Second", 50)
AddGadgetColumn(#ListSecondary, 2, "Third", 50)
; Add some dummy columns to our secondary listicon.
If CreatePopupMenu(#MenuPrimary)
; Create the popup menu used on our primary listview.
MenuItem(#MenuPrimaryTest, "Show Primary Header Info")
; Create a test sub-menu item.
EndIf
;
If CreatePopupMenu(#MenuSecondary)
; Create the popup menu used on our secondary listview.
MenuItem(#MenuSecondaryTest, "Show Secondary Header Info")
; Create a test sub-menu item.
EndIf
;
AddGadgetItem(#ListPrimary, -1, "1" + Chr(10) + "One" + Chr(10) + "This")
AddGadgetItem(#ListPrimary, -1, "2" + Chr(10) + "Two" + Chr(10) + "is")
AddGadgetItem(#ListPrimary, -1, "3" + Chr(10) + "Three" + Chr(10) + "a")
AddGadgetItem(#ListPrimary, -1, "4" + Chr(10) + "Four" + Chr(10) + "test.")
; Add some items to our primary listview control.
AddGadgetItem(#ListSecondary, -1, "1978" + Chr(10) + "The" + Chr(10) + "quick")
AddGadgetItem(#ListSecondary, -1, "2001" + Chr(10) + "brown" + Chr(10) + "fox")
AddGadgetItem(#ListSecondary, -1, "2005" + Chr(10) + "jumped" + Chr(10) + "over")
AddGadgetItem(#ListSecondary, -1, "2050" + Chr(10) + "the" + Chr(10) + "two")
AddGadgetItem(#ListSecondary, -1, "3009" + Chr(10) + "lazy" + Chr(10) + "dogs.")
; Add some items to our secondary listview control.
Repeat
;
EventID = WaitWindowEvent()
;
If EventID = #PB_Event_CloseWindow
; Close the program.
DoQuit = #True
;
ElseIf EventID = #PB_Event_Menu
; Menu Events
If EventMenuID() = #MenuPrimaryTest
;
MessageRequester("Primary Test", "The user clicked the second column in the primary listview control.", #PB_MessageRequester_Ok)
;
ElseIf EventMenuID() = #MenuSecondaryTest
;
MessageRequester("Secondary Test", "The user clicked the first column in the secondary listview control.", #PB_MessageRequester_Ok)
;
EndIf
;
ElseIf EventID = #PB_Event_Gadget
; Control Events
If EventGadgetID() = #ButtonClose And EventType() = #PB_EventType_LeftClick : DoQuit = #True : EndIf
; Clicked the close button, close the window.
EndIf
;
Until DoQuit = #True
;
EndIf

I tried to give credit where credit is due on the original tooltip functions so correct me if I missed something. I also tried to add decent comments so you can understand what's happening and how to adapt for your code.
Hope this helps! ^_^
EDIT:
Incidentally, you can tweak the tooltip in order to add some additional delays. In the AddTooltip() function look for the line...
Code: Select all
SendMessage_(hToolTip, #TTM_SETDELAYTIME, #TTDT_AUTOPOP, 15000)
You can add the next two lines to gain more control over the tooltip.
Code: Select all
SendMessage_(hToolTip, #TTM_SETDELAYTIME, #TTDT_INITIAL, 2000)
SendMessage_(hToolTip, #TTM_SETDELAYTIME, #TTDT_RESHOW, 1000)
These are pretty good values to keep so you don't get immediate tooltip popups.