Centering a WPF TreeViewItem in the TreeView ScrollViewer
In this article, Rick Strahl addresses a common usability issue with WPF’s TreeView, offering .NET code and helper methods to properly center selected items and improve user experience.
Centering a WPF TreeViewItem in the TreeView ScrollViewer
By Rick Strahl
The TreeView control in WPF can be challenging due to missing conveniences that developers often expect as standard, such as easily keeping a selected item visible and centered within the viewport. By default, the TreeView’s BringIntoView()
method only pulls an item just to the edge of the viewport (top or bottom), resulting in poor visibility and suboptimal UI experiences.
The Problem with BringIntoView
For example, when filtering and then re-unfiltering a list in a TreeView, you might want the focused item to remain clearly visible, ideally centered:1
Figure 1: BringIntoView() brings the TreeViewItem into view, but only at the edges (top or bottom), making the item less prominent.
The selected item can easily be missed when it sits at the very bottom, especially if the item is deeply nested. This is particularly problematic when working with documentation or data-tree UIs after a filter is reset and focus should be drawn to a specific item.
Standard Implementation and Its Drawbacks
The typical workaround involves trying to expand parent nodes, focus, and bring the TreeViewItem into view:
public void MakeTopicVisible(DocTopic topic) // bound data item {
if (topic == null) return;
var tvi = WindowUtilities.GetNestedTreeviewItem(topic, TreeTopicBrowser);
if (tvi == null) return;
// expand all parents
var tvParent = tvi;
while (tvParent?.Parent is TreeViewItem parentTvi) {
tvParent.IsExpanded = true;
if (tvParent.DataContext is DocTopic dt) dt.IsExpanded = true;
tvParent = parentTvi;
}
tvi.IsSelected = true;
tvi.Focus();
tvi.BringIntoView();
}
After all of this, the item may still only appear at the viewport’s edge. The BringIntoView()
method does not provide a way to center the target item.
Custom Centering Solution
To address this, Rick Strahl introduces a helper method to center the selected item manually by adjusting the ScrollViewer that hosts the TreeView control:
/// <summary>
/// Centers a TreeViewItem inside of the scrollviewer unlike ScrollIntoView
/// which scrolls the item just into the top or bottom if not already
/// in the view
/// </summary>
/// <param name="treeView">TreeView to scroll</param>
/// <param name="treeViewItem">TreeView Item to scroll to</param>
public static void CenterTreeViewItemInScrollViewer(TreeView treeView, TreeViewItem item) {
if (item == null) return;
// Ensure item is visible in layout
item.IsSelected = true;
item.BringIntoView();
treeView.Dispatcher.InvokeAsync(() => {
var scrollViewer = FindVisualChild<ScrollViewer>(treeView);
if (scrollViewer == null) return;
// Find the header content presenter
var header = FindVisualChild<ContentPresenter>(item);
if (header == null) return;
// Get header position relative to ScrollViewer
var transform = header.TransformToAncestor(scrollViewer);
var position = transform.Transform(new System.Windows.Point(0, 0));
double headerHeight = header.ActualHeight;
double viewportHeight = scrollViewer.ViewportHeight * scrollViewer.ScrollableHeight / scrollViewer.ExtentHeight;
double targetOffset = scrollViewer.VerticalOffset + position.Y - (viewportHeight / 2) + (headerHeight / 2);
scrollViewer.ScrollToVerticalOffset(targetOffset);
}, DispatcherPriority.Loaded);
}
This approach calculates the position of the item’s content within the ScrollViewer, ensuring that only the visible content (not children or expanded subtrees) is centered. This provides a more intuitive and prominent display.
Usage
Update your UI event code to use the new function:
tvi.IsSelected = true;
tvi.Focus();
WindowUtilities.CenterTreeViewItemInScrollViewer(TreeTopicBrowser, tvi);
Visual Result:
This code produces the desired behavior: the selected item is displayed in the center of the control, improving accessibility and focus.
Helper Methods
FindVisualChild Finds the first child control of a specified type in the visual tree:
public static T FindVisualChild<T>(DependencyObject currentControl) where T : DependencyObject {
if (currentControl != null) {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(currentControl); i++) {
var child = VisualTreeHelper.GetChild(currentControl, i);
if (child is T) {
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
GetNestedTreeViewItem Recursively searches for a TreeViewItem associated with a model value:
public static TreeViewItem GetNestedTreeviewItem(object item, ItemsControl parent) {
// look at this level
var tvi = parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (tvi != null) return tvi;
// otherwise, recurse into each generated TreeViewItem
foreach (object child in parent.Items) {
if (parent.ItemContainerGenerator.ContainerFromItem(child) is TreeViewItem childContainer) {
var result = GetNestedTreeviewItem(item, childContainer);
if (result != null) return result;
}
}
return null;
}
Additional Considerations
While centering is a common requirement, you may wish to refine this code to skip scrolling if the item is already within the viewport, or to position items at the top or bottom instead. Such enhancements are possible by further adjusting the offset logic.
Summary
Many aspects of the WPF TreeView control do not intuitively address hierarchical UI needs out-of-the-box. Fortunately, custom helper methods like those presented here provide flexible solutions.
Keep these helpers in your toolkit, and consider refactoring common patterns like these into reusable utilities the next time you build a data-centric .NET desktop app.
© Rick Strahl, West Wind Technologies, 2005-2025
This post appeared first on “Rick Strahl’s Blog”. Read the entire article here
-
GIF images referenced are omitted in textual descriptions for clarity. ↩