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>


 

Comments (12)

  1. Jeff T says:

    You need to add the triggers for the MouseOver properties to MyListBoxStyle:

    tr = New Trigger

    tr.Property = ListBoxItem.IsMouseOverProperty

    tr.Value = True

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

    MyListboxStyle.Triggers.Add(tr)

    tr = New Trigger

    tr.Property = ListBoxItem.IsMouseOverProperty

    tr.Value = True

    MyListboxStyle.Triggers.Add(tr)

  2. In my last post, Use LINQ with WPF : Styles and DataTemplates in code , I showed how to use DataTemplates

  3. Calvin_Hsia says:

    Thank Jeff T: yes somehow an unimportant line got left out of this post. The correct code is in the next post, which shows setting both fore and back properties in 1 Setter.

  4. We can subclass the prior Windows Presentation Foundation (WPF) class called Browse that displays the

  5. We can subclass the prior Windows Presentation Foundation (WPF) class called Browse that displays the

  6. clue says:

    Thanks for the code. It helped me a lot. 😉