Whenever a node is selected in my treeview, it automatically does a horizontal scroll to that item. Is there a way to disable this?
I had a DataGrid that I wanted to do the same operation on and used POHB's answer mostly. I had to modify it for my solution. The code is shown below. The datagrid is a 2 x 2 datagrid with the first column being thin and the second being very wide (1000+). The first column is frozen. I hope this helps someone out. -Matt
public partial class MyUserControl : UserControl
{
private ScrollContentPresenter _scrollContentPresenter;
private ScrollViewer _scrollViewer;
private double _dataGridHorizScrollPos = 0.0;
private bool _dataGridResetHorizScroll = false;
public MyUserControl()
{
// setup code...
_dataGrid.ApplyTemplate();
_scrollViewer = FindVisualChild<ScrollViewer>(_dataGrid);
_scrollViewer.ScrollChanged += new ScrollChangedEventHandler(DataGridScrollViewerScrollChanged);
_scrollContentPresenter = FindVisualChild<ScrollContentPresenter>(_scrollViewer);
_scrollContentPresenter.RequestBringIntoView += new RequestBringIntoViewEventHandler(_scrollContentPresenter_RequestBringInputView);
}
private void DataGridScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (_dataGridResetHorizScroll)
{
_scrollViewer.ScrollToHorizontalOffset(_dataGridHorizScrollPos);
}
// Note: When the row just before a page change is selected and then the next row on the
// next page is selected, a second event fires setting the horizontal offset to 0
// I'm ignoring those large changes by only recording the offset when it's large. -MRB
else if (Math.Abs(e.HorizontalChange) < 100)
{
_dataGridHorizScrollPos = _scrollViewer.HorizontalOffset;
}
_dataGridResetHorizScroll = false;
}
public T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if ((child != null) && (child is ScrollViewer))
{
// I needed this since the template wasn't applied yet when
// calling from the constructor
(child as ScrollViewer).ApplyTemplate();
}
if (child != null && child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
private void _scrollContentPresenter_RequestBringInputView(object sender, RequestBringIntoViewEventArgs e)
{
_dataGridResetHorizScroll = true;
}
To offer a slightly simplified version of @lena's answer:
To scroll vertically while preserving the horizontal scroll position, and with no unwanted side effects, in the XAML, add event handlers for RequestBringIntoView and Selected:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView"/>
<EventSetter Event="Selected" Handler="OnSelected"/>
...
In the code behind, add two event handlers:
private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
// Ignore re-entrant calls
if (mSuppressRequestBringIntoView)
return;
// Cancel the current scroll attempt
e.Handled = true;
// Call BringIntoView using a rectangle that extends into "negative space" to the left of our
// actual control. This allows the vertical scrolling behaviour to operate without adversely
// affecting the current horizontal scroll position.
mSuppressRequestBringIntoView = true;
TreeViewItem tvi = sender as TreeViewItem;
if (tvi != null)
{
Rect newTargetRect = new Rect(-1000, 0, tvi.ActualWidth + 1000, tvi.ActualHeight);
tvi.BringIntoView(newTargetRect);
}
mSuppressRequestBringIntoView = false;
}
private bool mSuppressRequestBringIntoView;
// Correctly handle programmatically selected items
private void OnSelected(object sender, RoutedEventArgs e)
{
((TreeViewItem)sender).BringIntoView();
e.Handled = true;
}