How to Use BringPathIntoView Method
This tutorial describes how to bring a virtualized item into view using the RadTreeView.BringPathIntoView() method.
It will guids you through the implementation of two common RadTreeView scenarios:
- Bring a virtualized item, that isn't in the viewport, into view and select it;
- Add a new item in the RadTreeView.ItemsSource collection, bring it into view and select it.
Set Up the Business Models
Let's start by setting up the business models that will define the hierarchy displayed inside the RadTreeView control. First we'll need a ViewModel describing the items in the tree. Please have in mind that in order to use the BringPathIntoView() method, we need to have the path to each item. This is why we will build a method inside each business item that constructs its full path. However, in order to do so, we'll have to keep a reference to the parent of each item.
public class BusinessItem
{
public BusinessItem(BusinessItem parent)
{
this.Items = new ObservableCollection<BusinessItem>();
this.Parent = parent;
}
public ObservableCollection<BusinessItem> Items { get; set; }
public BusinessItem Parent { get; private set; }
public string Name { get; set; }
public string GetPath()
{
string path = this.Name;
BusinessItem nextParent = this.Parent;
while (nextParent != null)
{
path = nextParent.Name + @"\" + path;
nextParent = nextParent.Parent;
}
return path;
}
}
Public Class BusinessItem
Public Sub New(ByVal parent As BusinessItem)
Me.Items = New ObservableCollection(Of BusinessItem)()
Me.Parent = parent
End Sub
Public Property Items() As ObservableCollection(Of BusinessItem)
Private privateParent As BusinessItem
Public Property Parent() As BusinessItem
Get
Return privateParent
End Get
Private Set(ByVal value As BusinessItem)
privateParent = value
End Set
End Property
Public Property Name() As String
Public Function GetPath() As String
Dim path As String = Me.Name
Dim nextParent As BusinessItem = Me.Parent
Do While nextParent IsNot Nothing
path = nextParent.Name & "\" & path
nextParent = nextParent.Parent
Loop
Return path
End Function
End Class
In order to complete the BusinessItem class implementation, we'll only add an IsSelected property so that we can easily control the selected state of each RadTreeViewItem. We'll also have to implement the INotifyPropertyChanged interface in order to notify the view (respectively the RadTreeView) for any changes in the value of the IsSelected property.
public class BusinessItem : INotifyPropertyChanged
{
...
private bool selected;
public bool IsSelected
{
get
{
return selected;
}
set
{
if (value != selected)
{
selected = value;
OnPropertyChanged("IsSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Public Class BusinessItem
Implements INotifyPropertyChanged
...
Private selected As Boolean
Public Property IsSelected() As Boolean
Get
Return selected
End Get
Set(ByVal value As Boolean)
If value <> selected Then
selected = value
OnPropertyChanged("IsSelected")
End If
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Next, we need a ViewModel describing the collection of BusinessItems. For the purpose of this tutorial, we'll create a SampleViewModel inheriting an ObservableCollection of BusinessItems and we will populate it with items:
public class SampleViewModel : ObservableCollection<BusinessItem>
{
public SampleViewModel()
{
for (int i = 0; i < 100; i++)
{
BusinessItem bi1 = new BusinessItem(null)
{
Name = i.ToString(),
IsSelected = false
};
for (int j = 0; j < 5; j++)
{
BusinessItem bi2 = new BusinessItem(bi1)
{
Name = bi1.Name + "." + j,
IsSelected = false
};
for (int k = 0; k < 5; k++)
{
BusinessItem bi3 = new BusinessItem(bi2)
{
Name = bi2.Name + "." + k,
IsSelected = false
};
bi2.Items.Add(bi3);
for (int l = 0; l < 5; l++)
{
BusinessItem bi4 = new BusinessItem(bi3)
{
Name = bi3.Name + "." + l,
IsSelected = false
};
bi3.Items.Add(bi4);
}
}
bi1.Items.Add(bi2);
}
this.Add(bi1);
}
}
}
Public Class SampleViewModel
Inherits ObservableCollection(Of BusinessItem)
Public Sub New()
For i As Integer = 0 To 99
Dim bi1 As New BusinessItem(Nothing) With {.Name = i.ToString(), .IsSelected = False}
For j As Integer = 0 To 4
Dim bi2 As New BusinessItem(bi1) With {.Name = bi1.Name & "." & j, .IsSelected = False}
For k As Integer = 0 To 4
Dim bi3 As New BusinessItem(bi2) With {.Name = bi2.Name & "." & k, .IsSelected = False}
bi2.Items.Add(bi3)
For l As Integer = 0 To 4
Dim bi4 As New BusinessItem(bi3) With {.Name = bi3.Name & "." & l, .IsSelected = False}
bi3.Items.Add(bi4)
Next l
Next k
bi1.Items.Add(bi2)
Next j
Me.Add(bi1)
Next i
End Sub
End Class
Finally, in order to make this example a bit more user-friendly, we will allow our users to bring an item into view, just by entering its header in a TextBox and hitting a BringIntoView button. In order to implement this functionality, we'll need a method that finds a BusinessItem based on its Header. This is why, we will extend the SampleViewModel definition by implementing a GetItemByName() method:
public class SampleViewModel : ObservableCollection<BusinessItem>
{
public BusinessItem GetItemByName(string name)
{
BusinessItem item = null;
string[] parts = name == null ? new string[] { "0" } : name.Split('.');
foreach (string s in parts)
{
try
{
int index = Int32.Parse(s);
if (item == null)
{
item = (index >= 0 && index < this.Items.Count) ? this[index] : null;
}
else
{
item = (index >= 0 && index < item.Items.Count) ? item.Items[index] : null;
}
}
catch
{
item = null;
break;
}
}
return item;
}
...
}
}
Public Class SampleViewModel
Inherits ObservableCollection(Of BusinessItem)
Public Function GetItemByName(ByVal name As String) As BusinessItem
Dim item As BusinessItem = Nothing
Dim parts() As String = If(name Is Nothing, New String() { "0" }, name.Split("."c))
For Each s As String In parts
Try
Dim index As Integer = Int32.Parse(s)
If item Is Nothing Then
item = If(index >= 0 AndAlso index < Me.Items.Count, Me(index), Nothing)
Else
item = If(index >= 0 AndAlso index < item.Items.Count, item.Items(index), Nothing)
End If
Catch
item = Nothing
Exit For
End Try
Next s
Return item
End Function
...
End Class
Now that our ViewModels are all in place, we can define our view and the RadTreeView control.
Set Up the View
We can start by adding a sample RadTreeView definition. As we will be displaying a large collection of items, it's best to virtualize the control:
<telerik:RadTreeView x:Name="myTreeView" Width="300" IsVirtualizing="True">
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
</telerik:RadTreeView>
Please keep in mind that the BringPathIntoView() method will work properly only if you set the TextSearch.TextPath attached property. This is due to the fact that the method internally uses the TextPath value to match the path of each business item to its corresponding container of type RadTreeViewItem. Therefore the value of the property has to be the name of the business property that is used to create a path to each node.
As the BusinessItem class defines the Path through the Name property, we need to extend the RadTreeView definition by setting the TextSearch.TextPath property to Name. As our scenario requires us to keep track of the selection in the RadTreeView control, we'll also add a RadTreeViewItem Style to bind the IsSelected property to the appropriate data object.
<telerik:RadTreeView x:Name="myTreeView" Width="300" IsVirtualizing="True" telerik:TextSearch.TextPath="Name">
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
<telerik:RadTreeView.ItemContainerStyle>
<Style TargetType="telerik:RadTreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</telerik:RadTreeView.ItemContainerStyle>
</telerik:RadTreeView>
Next, we'll extend our UI by adding a few TextBoxes and Buttons to allow our users to enter a header of an item and bring it into view. We'll also allow the users to add new items in the RadTreeView by entering the Header of their parent.
<StackPanel Orientation="Horizontal">
<telerik:RadTreeView x:Name="myTreeView"
Width="300"
IsVirtualizing="True"
telerik:TextSearch.TextPath="Name">
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
<telerik:RadTreeView.ItemContainerStyle>
<Style TargetType="telerik:RadTreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</telerik:RadTreeView.ItemContainerStyle>
</telerik:RadTreeView>
<StackPanel>
<Button Height="30"
Click="BringItem"
Content="Bring Item" />
<TextBox x:Name="textBox"
Width="300"
Height="30" />
<TextBlock Margin="0,20,0,0" Text="Add new item and bring it into view" />
<TextBlock Text="Add item in parent : " />
<TextBox x:Name="parentBox"
Width="300"
Height="30"
Text="Name of the parent..." />
<Button Height="30"
Click="AddItem"
Content="Add Item" />
</StackPanel>
</StackPanel>
Set Up the Code-Behind Logic
Now that we've defined both our viewmodels and our view, all we have left is to implement our code-behind logic for bringing an item into view and for adding a new item in the RadTreeView.ItemsSource collection.
First, we'll set the ItemsSource of the RadTreeView and immediately bring the 90.3.3.3 item into view.
public partial class Example : UserControl
{
private SampleViewModel sampleVM;
public Example()
{
InitializeComponent();
sampleVM = new SampleViewModel();
myTreeView.ItemsSource = sampleVM;
this.textBox.Text = "90.3.3.3";
BusinessItem item = this.sampleVM.GetItemByName(this.textBox.Text);
if (item != null)
{
item.IsSelected = true;
string path = item.GetPath();
myTreeView.BringPathIntoView(path);
}
}
}
Partial Public Class Example
Inherits UserControl
Private sampleVM As SampleViewModel
Public Sub New()
InitializeComponent()
sampleVM = New SampleViewModel()
myTreeView.ItemsSource = sampleVM
Me.textBox.Text = "90.3.3.3"
Dim item As BusinessItem = Me.sampleVM.GetItemByName(Me.textBox.Text)
If item IsNot Nothing Then
item.IsSelected = True
Dim path As String = item.GetPath()
myTreeView.BringPathIntoView(path)
End If
End Sub
End Class
The above logic will build the RadTreeView Items collection, and as soon as the RadTreeView is properly loaded, the BringPathIntoView() method will find and bring the item with Header of 90.3.3.3 into view.
Next, we will need to implement the Click event handler of the Bring Item button. It has to use the header entered by the user to find the BusienssItem that has to be brought into view and this is why we'll call the SampleViewModel.GetItemByName() method. Then as soon as we find the item, we will set its IsSelected property to True and find its path. Then we can use the RadTreeView.BringPathIntoView() method to bring it into view:
private void BringItem(object sender, RoutedEventArgs e)
{
BusinessItem item = this.sampleVM.GetItemByName(this.textBox.Text);
if (item != null)
{
item.IsSelected = true;
string path = item.GetPath();
myTreeView.BringPathIntoView(path);
}
}
Private Sub BringItem(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim item As BusinessItem = Me.sampleVM.GetItemByName(Me.textBox.Text)
If item IsNot Nothing Then
item.IsSelected = True
Dim path As String = item.GetPath()
myTreeView.BringPathIntoView(path)
End If
End Sub
Finally, we have to implement a logic that adds a new item when the Add Item button is clicked. As this button should create a new item in the ItemsSource collection of an item defined by the user, we again have to use the SampleViewModel.GetItemByName() method to find the business object that will parent the new item. Then we can add and bring the new item into view:
private void AddItem(object sender, RoutedEventArgs e)
{
BusinessItem parent = this.sampleVM.GetItemByName(this.parentBox.Text);
if (parent != null)
{
BusinessItem newItem = new BusinessItem(parent)
{
Name = parent.Name + "." + parent.Items.Count,
IsSelected = true
};
parent.Items.Add(newItem);
string path = newItem.GetPath();
this.myTreeView.BringPathIntoView(path);
}
}
Private Sub AddItem(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim parent As BusinessItem = Me.sampleVM.GetItemByName(Me.parentBox.Text)
If parent IsNot Nothing Then
Dim newItem As New BusinessItem(parent) With {.Name = parent.Name & "." & parent.Items.Count, .IsSelected = True}
parent.Items.Add(newItem)
Dim path As String = newItem.GetPath()
Me.myTreeView.BringPathIntoView(path)
End If
End Sub
Find a complete solution showing this approach in the WPF Samples GitHub repository