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>
Comments
Anonymous
November 15, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/11/15/use-datatemplates-and-wpf-in-code-to-create-a-general-purpose-linq-query-results-display/Anonymous
November 15, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/11/15/use-datatemplates-and-wpf-in-code-to-create-a-general-purpose-linq-query-results-display-2/Anonymous
November 21, 2007
We can subclass the prior Windows Presentation Foundation (WPF) class called Browse that displays theAnonymous
November 21, 2007
We can subclass the prior Windows Presentation Foundation (WPF) class called Browse that displays the