An interesting question came up recently, where someone wanted to have a TreeView in which:
- Each TreeViewItem consisted of two separate lines of content
- but the second line may be hidden, depending on its content.
This was actually a Windows Forms question, but I wondered if this would be a good candidate for harnessing the graphical power of WPF to solve a tough WinForms problem. My idea was to create a WPF UserControl that had the features described above and then host this User Control in an ElementHost in the Windows Form.
It seemed like a reasonable proposition and here's how I went about it.
Data Source
First of all I need some data, so will use a simple Person class.
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class Person
Implements INotifyPropertyChanged
Sub New(ByVal personname As String, ByVal personstatus As String, ByVal personcategory As String)
Me.FullName = personname
Me.Status = personstatus
Me.Category = personcategory
End Sub
Private _name As String
Public Property FullName() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
OnPropertyChanged(New PropertyChangedEventArgs("FullName"))
End Set
End Property
Private _status As String
Public Property Status() As String
Get
Return _status
End Get
Set(ByVal value As String)
_status = value
OnPropertyChanged(New PropertyChangedEventArgs("Status"))
End Set
End Property
Private _Category As String
Public Property Category() As String
Get
Return _Category
End Get
Set(ByVal value As String)
_Category = value
OnPropertyChanged(New PropertyChangedEventArgs("Category"))
End Set
End Property
Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
If Not PropertyChangedEvent Is Nothing Then
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Shared Function GetPersons() As List(Of Person)
Dim GP As New List(Of Person)
GP.Add(New Person("Ged Mead", "Busy", "VB City"))
GP.Add(New Person("Joe Brown", "On Site", "Work Colleagues"))
GP.Add(New Person("Serge Baliansky", "North Office", "VB City"))
GP.Add(New Person("Fran Pickman", "East Centre", "Family"))
GP.Add(New Person("Elaine Jones", "Not Available", "Work Colleagues"))
GP.Add(New Person("Matt Bianca", "South Office", "VB City"))
Return GP
End Function
End Class
As you see, this class implements INotifyPropertyChanged, so that changes to instance properties can be caught and any appropriate action taken as a result. I also included a basic method to generate some dummy data.
The Basic TreeView
The TreeView will be a WPF UserControl, so it is necessary to create this inside the Windows Forms project. Use the Project > Add New Item menu for this, then select WPF from the selection window that appears and name the WPF UserControl:

In the code-behind, you create a DataContext which will be the source of the data that is used to populate the TreeView:
Partial Public Class TwoLineTreeView
Dim Contacts As New List(Of Person)
Private Sub TwoLineTreeView_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Contacts = Person.GetPersons
Me.DataContext = Contacts
End Sub
End Class
An easy way to package up the two line content display is to use two TextBlocks inside a StackPanel. These would all be stored in a DataTemplate that the TreeView will use for its ItemTemplate property. Here's the first pass at the DataTemplate which I have placed in the Resources section of the UserControl named TwoLineTreeView.
<UserControl.Resources>
<DataTemplate x:Key="TVVis" >
<TreeViewItem IsExpanded="True">
<StackPanel>
<TextBlock Text="{Binding Path=FullName}"
Margin="2,0,0,1"></TextBlock>
<TextBlock Text="{Binding Path=Status}"
Margin="8,3,2,2"></TextBlock>
</StackPanel>
</TreeViewItem>
</DataTemplate>
</UserControl.Resources>
The first TextBlock binds to the Person's name and the second one binds to their Status.
We can test this early stage by adding a TreeView control to the UserControl:
<Grid>
<TreeView ItemsSource="{Binding}"
ItemTemplate="{StaticResource TVVis}" >
</TreeView>
</Grid>
The Grid is inserted in the UserControl by default and although we don't really need it in this example there's no great value in deleting it.
If you now move to the Windows Form, you can add an ElementHost and populate it with this data bound user control. You can either find the ElementHost control in the Toolbox and drag an instance of it on to the surface of the form. If it hasn't been added to the Toolbox (and as it is a Windows Forms project it doesn't always appear by default) you can simply find the UserControl itself in the Toolbox and drag this on to the Form.

When you do so, an ElementHost will be created for you on the Form and will automatically offer you the TwoLineTreeView user control as the Child of that ElementHost. Run the project and you will see:

Enabling and Disabling Visibility of the Second TextBlock
The easiest way of hiding or showing the second item of data (the status of the Person) is to use a ValueConverter. I've written several blogs describing the use of these and you can check them out if you need more detail on how they work. An introduction to a simple ValueConverter is covered here and a more complex verion here and here.
Here is the Converter class used in this project:
Imports System.Windows.Data
Public Class StatusToVisibilityConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Select Case value.ToString
Case "Busy", "Not Available", "En Route"
Return "Collapsed"
Case Else
Return "Visible"
End Select
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
It tests the value of the Status property and if it finds it is set to Busy, Not Available or En Route it will not display the line. It will display it in all other cases.
The UserControl requires a namespace mapping before we can use this class to create and use a converter in the XAML pane.
xmlns:local="clr-namespace:HostedTreeView"
With that in place, a converter is created inside the UserControl's Resources collection:
<local:StatusToVisibilityConverter x:Key="StatusConverter" />
The final step is to use the converter in the markup for the second TextBlock, specifically to hide or show it based on the content. Here is the amended TextBlock markup:
<TextBlock Text="{Binding Path=Status}"
Visibility="{Binding Path=Status, Converter={StaticResource StatusConverter}}"
Margin="8,3,2,2"></TextBlock>
Now when the project is run, the two Person instances that contain Busy or Not Available as their Status will not display the second line.

You can of course take this basic approach and use alternatives or weave a much more complex set of interconnections - perhaps replacing the Status text with another message altogether or totally hiding a Person and their Status if their Status has a particular value. Or whatever other variation meets your needs. They key task was to create a two line node in which you could control the visibility of the lines.
You can download the project used in this blog from here.