Udostępnij za pośrednictwem


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>