Udostępnij za pośrednictwem


Using and styling a treeview in WPF

 

As you know, a treeview control is very useful for viewing hierarchical information. Each node in a tree can have its own parent/child relationships.

 

Some of these trees can be extremely large, and can thus be prohibitively expensive to calculate completely.

Windows Explorer comes to mind. Outlook Mailbox folders. The game of chess.

 

Here’s a hypothesis: The tree of possible chess games is finite:

· There’s a finite number of chess pieces on a board at any time.

· At any given board position each piece has a finite number of moves

· A draw is the result of 3 repeated positions

· A draw is the result of 50 moves without a pawn move or capture

 

So, we could enumerate the tree to have a node representing each possible board position. The parent is the prior move. The children are the positions resulting from the possible moves from that position.

We can calculate the average fanout (average number of children per node, or moves from a position) of the tree. From the starting position, each pawn can move 2 possible squares and each knight can move 2. That’s 8 pawns* 2 + 2 knights * 2 = 18 possible positions after the 1st move. Let’s call it 20 moves per position.

 

The number of moves in a chess game (White, then Black, then White is 3 moves) is usually way less than 100 until checkmate, resignation, or draw. Let’s call it 100.

 

Thus we have about 20 ^ 100 moves. Let’s calculate the power of 10, or the number of zeros.

 

From my slide rule days (remember those? Before the calculator!) I remember memorizing the base 10 logarithm of the integers from 1-10.

Of course, this is pretty trivial for some numbers:

 

N Log10(N) Notes

1 0 Trivial: 10^0 = 1

2 .3010 Gotta remember it: 10^.3010 = 2

3 .4771 Remember it

4 .6020 Just twice Log10(2)

5 .6990 ez to remember

6 .7782 Just Log10(2) + Log10(3)

7 .8451 kinda harder to remember

8 .9031 3 Log10(2)

9 .9542 2 Log10(3)

10 1 not so hard: 10^1 = 1J

 

So it really comes down to remembering the primes >1 and < 10 = 2,3,5,7 which is only 4 numbers! Remembering these 4 numbers allows a huge range of order of magnitude (power of 10) calculations.

 

Getting back to our problem, we want to know x where 10^x = 20^100.

 

From the laws of logarithms and the table,

x = Log10(20^100) = 100 * (Log10(4*5)) = 100 * (log10(4) + log10(5)) = 100 * (2 * .3010 + .6990) = 100 * (.1301) = 130

 

Thus 20^ 100 = 10 ^ 130 (Go ahead: try it on a calculator!)

 

(See the Shannon number indicating that the number is roughly 10^120 )

 

This number is way more than a googol and way more than the number of electrons in the universe! And is thus effectively impractical to compute.

 

In any case, getting back to our little TreeView sample

 

Start VS 2010 File->New->Project->VB->WPF Application. Replace the Mainwindow.Xaml.vb with the code below.

Make sure that the xmlns in the inline XML in the code matches the project and namespace name. I chose “WpfApplication1” for both the project and the namespace for my sample.

Hit F5 to run the code.

 

Note the time it takes for the Title of the window to indicate the total number of nodes before the treeview is visible. Thus calculating the tree seems faster than rendering it.

Try moving around the tree, and hovering the mouse over an expanded item to see the MouseOver event trigger change the colors.

 

The code shows the inheritance hierarchies of a few classes in the WPF namespace, and can be completely calculated in a few seconds, resulting in about 3600 nodes.

 

Try commenting out the assignment of the ItemContainerStyle in the MyTreeViewItem constructor to see how styles propagate.

 

When you bring up Windows Explorer (Windows Key + E), you can see a tree representation of the files on your disk. Obviously, the designers of that treeview wanted to make it appear quickly, so only the top level nodes are shown (perhaps a few dozen) rather than calculating the entire tree (perhaps millions of nodes).

 

Creating child nodes dynamically only upon request, (the user chooses a particular child from perhaps dozens and clicks to expand it) is a great technique.

 

See also

Dynamically create huge tooltips in WPF TreeView and ListView

Returning data from a recursive method

Remove double spaces from pasted code samples in blog

<Code>

Imports System.Reflection

 

Class MainWindow

    Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        Try

            Me.Width = 800

            Me.Height = 800

            Dim treeview = New MyTreeView

            Me.Content = treeview

            Dim asm = Assembly.GetAssembly(GetType(TreeView)) ' PresentationFramework

            For Each typ In asm.GetExportedTypes

                FillData(treeview, typ)

            Next

            Me.Title = String.Format("Num items = {0} Asm = {1}", MyTreeViewItem._nTotItems, asm.FullName)

        Catch ex As Exception

            Me.Content = ex.ToString

        End Try

    End Sub

 

    Public Sub FillData(ByVal itemsControl As ItemsControl, ByVal typ As Type)

        Dim childItem = New MyTreeViewItem With {.Header = String.Format("{0}", typ.Name)}

 

        itemsControl.Items.Add(childItem)

 

        If typ.BaseType IsNot Nothing Then

            FillData(childItem, typ.BaseType) ' recur

            'childItem.IsExpanded = True ' expand the node: takes more time!

        End If

        'For Each mem In typ.GetMembers

        ' itemsControl.Items.Add(New MyTreeViewItem With {.Header = mem.Name})

        'Next

 

    End Sub

 

End Class

 

Public Class MyTreeView

    Inherits TreeView

    Friend Shared myStyle As Windows.Style

    Sub New()

        '"Segoe UI" "Consolas" "Tahoma" "Calibri" "Lucida Console" "Lucida Sans Typewriter" "MS Gothic" "MS Mincho" "David" "Modern No. 20"

        Dim _FontName = "Segoe UI"

        Dim _FontSize = "8"

        Dim _Foreground = "Blue"

        Dim _Background = "AntiqueWhite"

        ' https://msdn.microsoft.com/en-us/library/aa358802(v=VS.85).aspx

        'Public Text_Colors() As String = {"AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "BlanchedAlmond",

        '"Blue", "BlueViolet", "Brown", "BurlyWood", "CadetBlue", "Chartreuse", "Chocolate", "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan",

        '"DarkBlue", "DarkCyan", "DarkGoldenrod", "DarkGray", "DarkGreen", "DarkKhaki", "DarkMagenta", "DarkOliveGreen", "DarkOrange", "DarkOrchid", "DarkRed",

        '"DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", "DarkSlateGray", "DarkTurquoise", "DarkViolet", "DeepPink", "DeepSkyBlue", "DimGray", "DodgerBlue", "Firebrick",

        '"FloralWhite", "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", "Goldenrod", "Gray", "Green", "GreenYellow", "Honeydew", "HotPink", "IndianRed", "Indigo",

        '"Ivory", "Khaki", "Lavender", "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue", "LightCoral", "LightCyan", "LightGoldenrodYellow", "LightGray", "LightGreen",

        '"LightPink", "LightSalmon", "LightSeaGreen", "LightSkyBlue", "LightSlateGray", "LightSteelBlue", "LightYellow", "Lime", "LimeGreen", "Linen", "Magenta", "Maroon",

        '"MediumAquamarine", "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen", "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed",

        '"MidnightBlue", "MintCream", "MistyRose", "Moccasin", "NavajoWhite", "Navy", "OldLace", "Olive", "OliveDrab", "Orange", "OrangeRed", "Orchid", "PaleGoldenrod",

        '"PaleGreen", "PaleTurquoise", "PaleVioletRed", "PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", "Purple", "Red", "RosyBrown", "RoyalBlue", "SaddleBrown",

        '"Salmon", "SandyBrown", "SeaGreen", "SeaShell", "Sienna", "Silver", "SkyBlue", "SlateBlue", "SlateGray", "Snow", "SpringGreen", "SteelBlue", "Tan", "Teal", "Thistle",

   '"Tomato", "Transparent", "Turquoise", "Violet", "Wheat", "White", "WhiteSmoke", "Yellow", "YellowGreen"}

 

        'Note: make sure the 3rd xmlns has the right namespace and assembly name

        Dim XAMLtvItemStyle = _

<Style

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:l="clr-namespace:WpfApplication1;assembly=WpfApplication1"

    TargetType="{x:Type TypeName=l:MyTreeViewItem}">

    <Setter Property="FontFamily" Value=<%= _FontName %>/>

    <Setter Property="FontSize" Value=<%= _FontSize.ToString + "pt" %>/>

    <Setter Property="Foreground" Value=<%= _Foreground %>/>

    <Setter Property="Background" Value=<%= _Background %>/>

    <Style.Triggers>

        <Trigger Property="IsMouseOver" Value="True">

            <Setter Property="Foreground" Value="DarkRed"/>

            <Setter Property="Background" Value="LightGray"/>

        </Trigger>

    </Style.Triggers>

</Style>

 

        myStyle = CType(Windows.Markup.XamlReader.Load(XAMLtvItemStyle.CreateReader), Windows.Style)

        ' examine the Style properties at a breakpoint

 

        Me.ItemContainerStyle = myStyle

        ' here we can create a new contextmenu which will be accessible from every node

        'Me.ContextMenu = New ContextMenu

        'Me.ContextMenu.AddMnuItem("_DumpChildren To Notepad", "Dump expanded children to notepad", AddressOf on_ctxMenuItemBase)

    'Me.ContextMenu.AddMnuItem("_Expand SubTree", "Expand tree branch from here: warning: try small branches first!", AddressOf on_ctxMenuItemBase)

 

    End Sub

 

End Class

 

Public Class MyTreeViewItem

    Inherits TreeViewItem

    Friend Shared _nTotItems As Integer

    Sub New()

        Me.ItemContainerStyle = MyTreeView.myStyle ' try commenting this line out and expanding nodes

        _nTotItems += 1

    End Sub

End Class

 

 

</Code>