防止TreeView中的自动水平滚动

时间:2010-07-12 04:46:13

标签: wpf

每当在我的树视图中选择一个节点时,它会自动对该项目进行水平滚动。有没有办法禁用它?

8 个答案:

答案 0 :(得分:30)

处理RequestBringIntoView事件并将Handled设置为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;
}

答案 1 :(得分:12)

我设法使用以下方法解决问题:

<TreeView ScrollViewer.HorizontalScrollBarVisibility="Hidden">
   <TreeView.ItemsPanel>
      <ItemsPanelTemplate>
         <StackPanel MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ContentPresenter, AncestorLevel=1}}" />
      </ItemsPanelTemplate>
   </TreeView.ItemsPanel>
</TreeView>

我将在这里呈现ItemsPanel的StackPanel的宽度绑定到TreeView中的ContentPresenter的ActualWidth。

它也适用于“hacked”Stretching TreeView:http://blogs.msdn.com/b/jpricket/archive/2008/08/05/wpf-a-stretching-treeview.aspx(我修改了该解决方案不是为了删除网格列,而是将第一个Decorator元素的Grid.Column属性从1更改为2)。

答案 2 :(得分:6)

我有类似的问题。我需要防止水平滚动但保留垂直滚动。我的解决方案是处理OnRequestBringIntoView方法,因为我希望它能够表现出来。我为ResourceDictionary创建了TreeViewItem,并为EventSettersOnSelected方法添加了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;
        }
    }
}

答案 3 :(得分:6)

提供@ 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;
}

答案 4 :(得分:5)

Matthew,我设法保留垂直滚动,并且仅在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;
}

答案 5 :(得分:1)

以下解决方案更简单,经过全面测试,更兼容,您不需要计算和更改滚动条偏移,您需要将水平滚动条移动到左侧,因为&#34; RequestBringIntoView&#34;事件路由策略正在冒泡,您只需要在最后一个项目到达事件上执行此操作。 名称scrollViewer控件&#34; _tv_scrollviewer _&#34;

<TreeView>
<TreeView.ItemContainerStyle>
    <Style TargetType="{x:Type TreeViewItem}">
        <EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView"/>

关于代码背后:

    private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
    {
        var item = (TreeViewItem)sender;
        if (item != null)
        {
            // move horizontal scrollbar only when event reached last parent item
            if (item.Parent == null)
            {
                var scrollViewer = itemsTree.Template.FindName("_tv_scrollviewer_", itemsTree) as ScrollViewer;
                if (scrollViewer != null)
                    Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)(() => scrollViewer.ScrollToLeftEnd()));
            }
        }

    }

答案 6 :(得分:0)

我有一个DataGrid我想做同样的操作并且主要使用POHB的答案。我不得不为我的解决方案修改它。代码如下所示。 datagrid是一个2 x 2数据网格,第一列很薄,第二列很宽(1000+)。第一列是冻结的。我希望这可以帮助别人。 -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;
      }

答案 7 :(得分:0)

@ 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正常滚动,如果该项目已经垂直在视口中。然而对于实际的#烦人的&#34; case(项目已经可见),它将e.Handled设置为true,从而防止水平滚动。