Use DataTemplates and WPF in code to create a general purpose LINQ Query results display

In my last post, Use LINQ with WPF : Styles and DataTemplates in code, I showed how to use DataTemplates in code to show the results of a query in a ListBox.

Let's make a reusable class called Browse which creates WPF content as a ListView from a LINQ query and generates columns and headers. It also uses DataTemplates to databind

The headers will sort the columns when clicked. Also, you can resize and change the order of columns by clicking/dragging.

(For a Windows Forms version of Browse (not WPF) , see Cool Linq Query)

Start Visual Studio 2008 (or 2005 with Visual Studio 2005 extensions for .NET Framework 3.0. Linq is only in 2008)

Choose File->New Project->Visual Basic->WPF Application

You can use the WPF Forms designer, or you can write your code in a program.

Double click the form designer to bring up the Window1.xaml.vb file. Replace the contents with the code below.

<Code Sample>

Imports System.Windows.Controls.Primitives ' for Popup

Partial Public Class Window1

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

        Me.Width = 800

        Me.Height = 800

        Dim Query = From proc In System.Diagnostics.Process.GetProcesses _

                    Select proc.Id, proc.ProcessName, ThreadCount = proc.Threads.Count, proc.MainWindowTitle

        Me.Content = New Browse(Query, Me) ' the Browse will fill the entire window

        'Me.Content = New BrowseWithPopup(Query, Me)

    End Sub

End Class

Class Browse

    Inherits ListView

    Sub New(ByVal Query As Object, Optional ByVal Parent As Object = Nothing)

        Dim gv As New GridView

        Me.View = gv

        Me.ItemsSource = Query

        If Not Parent Is Nothing Then

            If Parent.GetType.BaseType Is GetType(Window) Then

                CType(Parent, Window).Title = "# items = " + Me.Items.Count.ToString

            End If

        End If

        Me.AddHandler(GridViewColumnHeader.ClickEvent, New RoutedEventHandler(AddressOf HandleHeaderClick))

        For Each mem In From mbr In _

                        Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName) _

                            .GetGenericArguments()(0).GetMembers _

                        Where mbr.MemberType = Reflection.MemberTypes.Property

            Dim coltype = CType(mem, Reflection.PropertyInfo).PropertyType.Name

            Select Case coltype

                Case "Int32", "String"

                    Dim gvc As New GridViewColumn

              gvc.Header = mem.Name

                    gv.Columns.Add(gvc)

                    If coltype = "Int32" Then

                        gvc.Width = 80

                        Dim dt As New DataTemplate

                        Dim factSP = New FrameworkElementFactory(GetType(StackPanel))

                        dt.VisualTree = factSP

                        factSP.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal)

                        Dim factTb = New FrameworkElementFactory(GetType(TextBlock))

                        factTb.SetBinding(TextBlock.TextProperty, New Binding(mem.Name))

                        factTb.SetValue(TextBlock.FontWeightProperty, FontWeights.Bold)

                        ' factTb.SetValue(TextBlock.BackgroundProperty, Brushes.SpringGreen)

                        factSP.AppendChild(factTb)

                        'factTb = New FrameworkElementFactory(GetType(Button))

                        'factTb.SetValue(Button.ContentProperty, "hit me")

                        'factSP.AppendChild(factTb)

                        gvc.CellTemplate = dt

                    Else

                        gvc.DisplayMemberBinding = New Binding(mem.Name)

         gvc.Width = 180

                    End If

            End Select

        Next

        Dim MyListboxStyle As New Style(GetType(ListBoxItem))

        Dim tr = New Trigger

        tr.Property = ListBoxItem.IsSelectedProperty ' when Selected

        tr.Value = True ' is true

        tr.Setters.Add(New Setter(ListBoxItem.ForegroundProperty, Brushes.Bisque))

        MyListboxStyle.Triggers.Add(tr)

        tr = New Trigger

        tr.Property = ListBoxItem.IsMouseOverProperty ' when mouseover

        tr.Value = True ' is true

        tr.Setters.Add(New Setter(ListBoxItem.ForegroundProperty, Brushes.Red))

        tr.Setters.Add(New Setter(ListBoxItem.BackgroundProperty, Brushes.Aquamarine)) ' this trigger sets both fore and back properties

        MyListboxStyle.Setters.Add(New Setter(ListBoxItem.ForegroundProperty, Brushes.RoyalBlue)) ' set style for all items

        MyListboxStyle.Triggers.Add(tr)

        Me.ItemContainerStyle = MyListboxStyle

    End Sub

    Dim _Lastdir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

    Dim _LastHeaderClicked As GridViewColumnHeader = Nothing

    Sub HandleHeaderClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If e.OriginalSource.GetType Is GetType(GridViewColumnHeader) Then

            Dim gvh = CType(e.OriginalSource, GridViewColumnHeader)

            Dim dir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

            If Not gvh Is Nothing AndAlso Not gvh.Column Is Nothing Then

                Dim hdr = gvh.Column.Header

                If gvh Is _LastHeaderClicked Then

                    If _Lastdir = ComponentModel.ListSortDirection.Ascending Then

                        dir = ComponentModel.ListSortDirection.Descending

                    End If

                End If

                Sort(hdr, dir)

                _LastHeaderClicked = gvh

                _Lastdir = dir

            End If

        End If

    End Sub

    Sub Sort(ByVal sortby As String, ByVal dir As System.ComponentModel.ListSortDirection)

        Me.Items.SortDescriptions.Clear()

        Dim sd = New System.ComponentModel.SortDescription(sortby, dir)

        Me.Items.SortDescriptions.Add(sd)

        Me.Items.Refresh()

    End Sub

End Class

</Code Sample>