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
Anonymous
November 12, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/11/12/use-linq-with-wpf-styles-and-datatemplates-in-code/Anonymous
November 13, 2007
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)Anonymous
November 15, 2007
In my last post, Use LINQ with WPF : Styles and DataTemplates in code , I showed how to use DataTemplatesAnonymous
November 15, 2007
The comment has been removedAnonymous
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 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-3/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-4/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 theAnonymous
March 28, 2008
PingBack from http://frankthefrank.info/entry.php?id=kwws%3d22ehwd1eorjv1pvgq1frp2fdoylqbkvld2dufklyh2533%3a2442452947986%3a1dvs%7bAnonymous
October 21, 2008
Thanks for the code. It helped me a lot. ;)