Use LINQ with WPF : Styles and DataTemplates in code

Using Windows Presentation Foundation (WPF) and LINQ is easy in code. We'll create a query and display it in a WPF ListBox

Using XAML is harder: the ObjectDataProvider class works with non-anonymous types.

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.

                                                                                                                 

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

        Dim MyListBox As New ListBox

        MyListBox.ItemsSource = Query

        ' lb.ItemsSource = New String() {"one", "two"} ' or a simple array

        Me.Content = MyListBox

    End Sub

End Class

Hit F5, and now you have a ListBox on a form which displays multiple rows like so:

{ Id = 2312, ProcessName = notepad, ThreadCount =1, MainWindowTitle = t.txt – Notepad, }

Let's make the display a little more friendly by using a DataTemplate.

Add this code before the End Sub:

        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("Id"))

        factTb.SetValue(TextBlock.WidthProperty, 40.0)

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

        factSP.AppendChild(factTb)

        factTb = New FrameworkElementFactory(GetType(TextBlock))

        factTb.SetBinding(TextBlock.TextProperty, New Binding("ProcessName"))

        factTb.SetValue(TextBlock.FontStyleProperty, FontStyles.Italic)

        factTb.SetValue(TextBlock.WidthProperty, 80.0)

        factSP.AppendChild(factTb)

        factTb = New FrameworkElementFactory(GetType(TextBlock))

        factTb.SetBinding(TextBlock.TextProperty, New Binding("ThreadCount"))

        factTb.SetValue(TextBlock.WidthProperty, 30.0)

        factSP.AppendChild(factTb)

        factTb = New FrameworkElementFactory(GetType(TextBlock))

        factTb.SetBinding(TextBlock.TextProperty, New Binding("MainWindowTitle"))

        factSP.AppendChild(factTb)

    MyListBox.ItemTemplate = dt

This code uses a Data Template to map data names with UI elements.

Because there are multiple rows, the template declaratively specifies how the data is presented.

In this case, the multiple rows of the query mapped to multiple ListBoxItems in the ListBox. Each ListBoxItem displays as a factory generated Horizontal StackPanel. The StackPanel contains a factory generated TextBlock for each field. WPF allows all sorts of controls inside other controls, so we could have a movie or even another listbox inside the listbox item.

Some of the TextBlocks have their width or FontStyle set. However, this code is hardcoded for the field names and data types of the query. Let's make it more generic. We can use refection to get the data types and field names of the anonymous type created by the query:

            Dim QueryAnonType = _

            Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName).GetGenericArguments()(0)

            For Each mem In QueryAnonType.GetMembers

                If mem.MemberType = Reflection.MemberTypes.Property Then

                    Select Case CType(mem, Reflection.PropertyInfo).PropertyType.Name

                        Case "Int32"

                            Dim factTb = New FrameworkElementFactory(GetType(TextBlock))

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

    factTb.SetValue(TextBlock.WidthProperty, 40.0)

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

                            factSP.AppendChild(factTb)

                        Case "String"

                            Dim factTb = New FrameworkElementFactory(GetType(TextBlock))

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

                            factTb.SetValue(TextBlock.WidthProperty, 130.0)

                            factTb.SetValue(TextBlock.FontStyleProperty, FontStyles.Italic)

                            factTb.SetValue(TextBlock.ForegroundProperty, Brushes.CornflowerBlue)

                            factSP.AppendChild(factTb)

                    End Select

                End If

            Next

Putting it all together, adding some style triggers for fun, the entire code sample is below.

Next time, we'll add headers that sort the columns when clicked and some

<Code Sample>

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

        Dim MyListBox As New ListBox

        MyListBox.ItemsSource = Query

        ' lb.ItemsSource = New String() {"one", "two"} ' or a simple array

        Me.Content = MyListBox

        Dim dt As New DataTemplate

        Dim factSP = New FrameworkElementFactory(GetType(StackPanel))

        dt.VisualTree = factSP

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

        Dim QueryAnonType = _

            Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName).GetGenericArguments()(0)

        For Each mem In QueryAnonType.GetMembers

            If mem.MemberType = Reflection.MemberTypes.Property Then

                Select Case CType(mem, Reflection.PropertyInfo).PropertyType.Name

                    Case "Int32"

                        Dim factTb = New FrameworkElementFactory(GetType(TextBlock))

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

                        factTb.SetValue(TextBlock.WidthProperty, 40.0)

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

                        factSP.AppendChild(factTb)

                    Case "String"

                        Dim factTb = New FrameworkElementFactory(GetType(TextBlock))

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

                        factTb.SetValue(TextBlock.WidthProperty, 130.0)

                        factTb.SetValue(TextBlock.FontStyleProperty, FontStyles.Italic)

                        factTb.SetValue(TextBlock.ForegroundProperty, Brushes.CornflowerBlue)

                        factSP.AppendChild(factTb)

                End Select

            End If

        Next

        MyListBox.ItemTemplate = dt

        Dim MyListboxStyle As New Style(GetType(ListBoxItem))

        Dim tr = New Trigger

        tr.Property = ListBoxItem.IsSelectedProperty

        tr.Value = True

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

        MyListboxStyle.Triggers.Add(tr)

        tr = New Trigger

        tr.Property = ListBoxItem.IsMouseOverProperty

        tr.Value = True

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

        tr = New Trigger

        tr.Property = ListBoxItem.IsMouseOverProperty

        tr.Value = True

        tr.Setters.Add(New Setter(ListBoxItem.BackgroundProperty, Brushes.Aquamarine))

        MyListboxStyle.Setters.Add(New Setter(ListBoxItem.ForegroundProperty, Brushes.Black))

        MyListboxStyle.Triggers.Add(tr)

        MyListBox.ItemContainerStyle = MyListboxStyle

  End Sub

End Class

</Code Sample>