5
votes

I have a scrollviewer wrapping a treeview.

I populate the treeview programmatically (it is not bound), and expand the treeview to a predetermined treeviewitem. That all works fine.

My problem is that when the tree expands I would like the scrollview that parents the treeview to scroll to the treeviewitem that I just expanded. Any ideas? - keep in mind that the treeview may not have the same structure each time it is expanded, so that rules out just storing the current scroll position and reseting to that...

2

2 Answers

4
votes

I've had the same issue, with the TreeView not scrolling to the selected item.

What I did was, after expanding the tree to the selected TreeViewItem, I called a Dispatcher Helper method to allow the UI to update, and then used the TransformToAncestor on the selected item, to find its position within the ScrollViewer. Here is the code:

    // Allow UI Rendering to Refresh
    DispatcherHelper.WaitForPriority();

    // Scroll to selected Item
    TreeViewItem tvi = myTreeView.SelectedItem as TreeViewItem;
    Point offset = tvi.TransformToAncestor(myScroll).Transform(new Point(0, 0));
    myScroll.ScrollToVerticalOffset(offset.Y);

Here is the DispatcherHelper code:

public class DispatcherHelper
{
    private static readonly DispatcherOperationCallback exitFrameCallback = ExitFrame;

    /// <summary>
    /// Processes all UI messages currently in the message queue.
    /// </summary>
    public static void WaitForPriority()
    {
        // Create new nested message pump.
        DispatcherFrame nestedFrame = new DispatcherFrame();

        // Dispatch a callback to the current message queue, when getting called,
        // this callback will end the nested message loop.
        // The priority of this callback should be lower than that of event message you want to process.
        DispatcherOperation exitOperation = Dispatcher.CurrentDispatcher.BeginInvoke(
            DispatcherPriority.ApplicationIdle, exitFrameCallback, nestedFrame);

        // pump the nested message loop, the nested message loop will immediately
        // process the messages left inside the message queue.
        Dispatcher.PushFrame(nestedFrame);

        // If the "exitFrame" callback is not finished, abort it.
        if (exitOperation.Status != DispatcherOperationStatus.Completed)
        {
            exitOperation.Abort();
        }
    }

    private static Object ExitFrame(Object state)
    {
        DispatcherFrame frame = state as DispatcherFrame;

        // Exit the nested message loop.
        frame.Continue = false;
        return null;
    }
}
2
votes

Jason's ScrollViewer trick is a great way of moving a TreeViewItem to a specific position.

One problem, though: in MVVM you do not have access to the ScrollViewer in the view model. Here is a way to get to it anyway. If you have a TreeViewItem, you can walk up its visual tree until you reach the embedded ScrollViewer:

// Get the TreeView's ScrollViewer
DependencyObject parent = VisualTreeHelper.GetParent(selectedTreeViewItem);
while (parent != null && !(parent is ScrollViewer))
{
    parent = VisualTreeHelper.GetParent(parent);
}