Подтвердить что ты не робот

Запретить автоматическую горизонтальную прокрутку в TreeView

Всякий раз, когда в моем дереве выбран node, он автоматически выполняет горизонтальную прокрутку к этому элементу. Есть ли способ отключить это?

4b9b3361

Ответ 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, тем самым предотвращая горизонтальную прокрутку.