Всякий раз, когда в моем дереве выбран node, он автоматически выполняет горизонтальную прокрутку к этому элементу. Есть ли способ отключить это?
Запретить автоматическую горизонтальную прокрутку в TreeView
Ответ 1
Обработайте событие RequestBringIntoView и установите для параметра Обработать значение true, и структура не будет пытаться отобразить этот элемент. Например, сделайте что-нибудь подобное в своем XAML:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
И затем это в вашем коде:
private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
e.Handled = true;
}
Ответ 2
Мне удалось решить проблему, используя следующее:
<TreeView ScrollViewer.HorizontalScrollBarVisibility="Hidden"
...another settings here...>
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ContentPresenter, AncestorLevel=1}}" />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
... another settings also here ...
</TreeView>
Я связываю ширину StackPanel, которая отображает ItemsPanel здесь, в ActualWidth ContentPresenter из TreeView.
BTW: Он также хорошо работает с "взломанным" растягивающим деревом: http://blogs.msdn.com/b/jpricket/archive/2008/08/05/wpf-a-stretching-treeview.aspx (я изменил это решение, чтобы не удалить столбец сетки, но для изменения свойства Grid.Column первого элемента Decorator от 1 до 2).
Надеюсь, это поможет.: -)
Ответ 3
Мэтью, мне удалось сохранить вертикальную прокрутку и только предотвращать горизонтальную прокрутку, восстанавливая горизонтальное положение после свитка, вызванного событием RequestBringIntoView.
private double treeViewHorizScrollPos = 0.0;
private bool treeViewResetHorizScroll = false;
private ScrollViewer treeViewScrollViewer = null;
private void TreeViewItemRequestBringIntoView( object sender, RequestBringIntoViewEventArgs e )
{
if ( this.treeViewScrollViewer == null )
{
this.treeViewScrollViewer = this.DetailsTree.Template.FindName( "_tv_scrollviewer_", this.DetailsTree ) as ScrollViewer;
if( this.treeViewScrollViewer != null )
this.treeViewScrollViewer.ScrollChanged += new ScrollChangedEventHandler( this.TreeViewScrollViewerScrollChanged );
}
this.treeViewResetHorizScroll = true;
this.treeViewHorizScrollPos = this.treeViewScrollViewer.HorizontalOffset;
}
private void TreeViewScrollViewerScrollChanged( object sender, ScrollChangedEventArgs e )
{
if ( this.treeViewResetHorizScroll )
this.treeViewScrollViewer.ScrollToHorizontalOffset( this.treeViewHorizScrollPos );
this.treeViewResetHorizScroll = false;
}
Ответ 4
У меня была аналогичная проблема. Мне нужно было предотвратить горизонтальную прокрутку, но сохранить вертикальный свиток. Мое решение состоит в том, чтобы обрабатывать метод OnRequestBringIntoView
, как я хочу, чтобы он себя вел. Я создал ResourceDictionary
для TreeViewItem
и добавил методы EventSetters
для OnSelected
и OnRequestBringIntoView
.
MyResourceDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" x:Class="Resources.MyResourceDictionary" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="TreeViewItem" x:Key="treeitem" >
<EventSetter Event="RequestBringIntoView" Handler="OnRequestBringIntoView"/>
<EventSetter Event="Selected" Handler="OnSelected"/>
</Style>
</ResourceDictionary>
MyResourceDictionary.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Resources
{
partial class MyResourceDictionary:ResourceDictionary
{
public MyResourceDictionary()
{
InitializeComponent();
}
private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
e.Handled = true; //prevent event bubbling
var item = (TreeViewItem)sender;
TreeView tree = GetParentTree(item) as TreeView;
if(tree!=null)
{
var scrollViewer = tree.Template.FindName("_tv_scrollviewer_", tree) as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToLeftEnd();//prevent horizontal scroll
Point relativePoint = item.TransformToAncestor(tree).Transform(new Point(0, 0));//get position of a selected item
if (relativePoint.Y <= scrollViewer.ContentVerticalOffset) return;//do no scroll if we select inside one 'scroll screen'
scrollViewer.ScrollToVerticalOffset(relativePoint.Y);//scroll to Y of a selected item
}
}
}
private DependencyObject GetParentTree(DependencyObject item)
{
var target = VisualTreeHelper.GetParent(item);
return target as TreeView != null ? target : GetParentTree(target);
}
private void OnSelected(object sender, RoutedEventArgs e) //handle programmatically selected items
{
var item = (TreeViewItem)sender;
item.BringIntoView();
e.Handled = true;
}
}
}
Ответ 5
Чтобы предложить немного упрощенную версию ответа @lena:
Чтобы прокручивать вертикально, сохраняя положение горизонтальной прокрутки и без нежелательных побочных эффектов, в XAML добавьте обработчики событий для RequestBringIntoView и Selected:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView"/>
<EventSetter Event="Selected" Handler="OnSelected"/>
...
В коде позади добавьте два обработчика событий:
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;
}
Ответ 6
У меня был DataGrid, в котором я хотел выполнить ту же операцию и в основном использовал ответ POHB. Мне пришлось изменить его для моего решения. Код показан ниже. Datagrid - это 2 x 2 datagrid, при этом первый столбец тонкий, а второй - очень широкий (1000+). Первый столбец заморожен. Надеюсь, это поможет кому-то. Матф
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 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;
}
Ответ 7
@lena решение сохранения вертикальной прокрутки работало лучше всего для меня. Я немного повторил это:
private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
var treeViewItem = (TreeViewItem)sender;
var scrollViewer = treeView.Template.FindName("_tv_scrollviewer_", treeView) as ScrollViewer;
Point topLeftInTreeViewCoordinates = treeViewItem.TransformToAncestor(treeView).Transform(new Point(0, 0));
var treeViewItemTop = topLeftInTreeViewCoordinates.Y;
if (treeViewItemTop < 0
|| treeViewItemTop + treeViewItem.ActualHeight > scrollViewer.ViewportHeight
|| treeViewItem.ActualHeight > scrollViewer.ViewportHeight)
{
// if the item is not visible or too "tall", don't do anything; let them scroll it into view
return;
}
// if the item is already fully within the viewport vertically, disallow horizontal scrolling
e.Handled = true;
}
Это означает, что ScrollViewer прокручивается нормально, если элемент еще не находится в окне просмотра по вертикали. Однако для реального "раздражающего" случая (когда элемент уже виден) он устанавливает e.Handled в true, тем самым предотвращая горизонтальную прокрутку.