Dynamically create huge tooltips in WPF TreeView and ListView
Tooltips are useful. When the mouse hovers over a button a tip can indicate what happens when it’s clicked. The mouse move does not actually invoke the button, but can give information in a passive way.
Sometimes I want to make huge tooltips. This essentially gives more screen real estate for presenting a lot of information, without having to sacrifice screen space of the main presentation.
For example, hovering over a URL might show in a tooltip the entire referenced page. Hover over the name of a movie’s filename and see the movie play in a tooltip just with a mouse move.
Clearly, these tooltips are much more expensive than a line of text indicating what a button does. They take time to construct and memory to store. If you attach the tooltip content to the object itself, it’s more static, rather than being generated at MouseMove time.
I use WPF ListViews and TreeViews to show millions of items, and I want to have huge tooltips for them. This is for a memory inspection tool which shows details about the allocation. These details include:
· Info about the allocation: size of allocation, when it was allocated, what thread
· the entire callstack (including native and managed code frames) of the memory allocation call (could be hundreds of lines)
· for a managed object, I also display the class layout information in a tooltip.
· a memory dump of the actual memory.
The tooltip content can be any WPF content, so I can have the memory dump be in a monospace font to align the columns of hex values.
Sure, this is a lot of info for a tip: the user merely needs to double click the item or choose the first context menu item to dump out the same tip info into a TXT file viewed in notepad.
Dynamically creating the content and controlling the tooltip behavior (so a slight mouse jiggle doesn’t cause the tip to flash) is pretty easy in WPF.
Try running the code below, move the mouse over some media. While the movie/picture is showing, move the mouse to see when the tooltip goes away. Since the PlacementTarget is the tree view item, as long as the mouse is over that item, the tip will remain rock steady.
Try leaving the mouse on the item to test how long it takes for the tooltip to disappear: important to make it stick around so the user can grok it.
Start VS 2010. File->New->Project->VB->Windows->WPF App
Replace the MainWindow.Xaml.vb file with the contents below, then hit F5
Next time: Right click on the On_MouseMove method and choose Create Unit tests. Just hit enter to the dialogs to accept the default method to test, and the test project to create
See also:
Create your own media browser: Display your pictures, music, movies in a XAML tooltip
(if you get “Error message when you try to access the My Documents, My Music, My Pictures, and My Videos folders in Windows Vista: "Access is Denied" see https://support.microsoft.com/kb/930128 )
<code sample>
Class MainWindow
Friend Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Try
Me.WindowState = Windows.WindowState.Maximized
Dim tv As New MyTreeView
Me.Content = tv
Dim filePath = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
AddRecursive(filePath, tv.Items)
Catch ex As Exception
Me.Content = ex.ToString
End Try
End Sub
Private Sub AddRecursive(ByVal filePath As String, ByVal itemCollection As ItemCollection)
Try
Dim dirs = IO.Directory.GetDirectories(filePath)
For Each directory In dirs
Dim itm = New TreeViewItem With {
.Header = New TextBlock With {
.Text = directory
}
}
itemCollection.Add(itm)
AddRecursive(directory, itm.Items)
Next
Dim files = IO.Directory.GetFiles(filePath, "*.*", IO.SearchOption.TopDirectoryOnly)
For Each fileName In files
itemCollection.Add(New TreeViewItem With {
.Header = New TextBlock With {
.Text = fileName
}
})
Next
Catch ex As Exception
End Try
End Sub
Friend Class MyTreeView
Inherits TreeView
Protected Friend _LastTipObj As FrameworkElement ' the control (like Textbox) that has the .ToolTip property
Public Sub ClearPriorTVToolTipIfAny()
If _LastTipObj IsNot Nothing Then
If _LastTipObj.ToolTip IsNot Nothing Then
Dim lastTip = CType(_LastTipObj.ToolTip, ToolTip)
lastTip.IsOpen = False
_LastTipObj.ToolTip = Nothing
End If
_LastTipObj = Nothing
End If
End Sub
Sub on_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
Try
Dim itm = TryCast(e.OriginalSource, TextBlock)
If itm IsNot Nothing Then
If _LastTipObj IsNot Nothing Then
If itm Is _LastTipObj Then ' same obj
Return
End If
ClearPriorTVToolTipIfAny()
End If
'the tip is potentially memory expensive, so we only want
' to keep one around
Dim finfo = New IO.FileInfo(itm.Text)
Dim tiptxt = String.Empty
If finfo.Exists Then
tiptxt = String.Format("{0} {1:n0}", finfo.ToString, finfo.Length)
Else
tiptxt = String.Format("{0}", finfo.ToString)
End If
Dim tipContent As UIElement
Dim ext = finfo.Extension.ToLower
If ext.Length > 0 AndAlso ".avi .jpg .mid .mpg .wma .wmv".Contains(ext) Then
tipContent = New MediaElement With
{
.Source = New Uri(itm.Text)
}
Else
tipContent = New TextBlock With {
.Background = Brushes.Azure,
.Text = tiptxt
}
End If
Dim ttipObj = New ToolTip With {
.Placement = Primitives.PlacementMode.Bottom,
.PlacementTarget = itm,
.Content = tipContent
}
itm.ToolTip = ttipObj
ttipObj.IsOpen = True ' funny syntax
_LastTipObj = itm
End If
Catch ex As Exception
End Try
End Sub
Sub On_MouseLeave() Handles Me.MouseLeave
Me.ClearPriorTVToolTipIfAny()
End Sub
End Class
End Class
</code sample>