WPF ListBox滚动到自动结束

nia*_*iao 40 wpf scroll listbox

在我的申请中,我有一个ListBox项目.该应用程序是用WPF编写的.

如何自动滚动到最后添加的项目?我希望在ScrollViewer添加新项目时将其移动到列表的末尾.

有什么事ItemsChanged吗?(我不想使用这个SelectionChanged活动)

Oz *_*ayt 41

试试这个:

lstBox.SelectedIndex = lstBox.Items.Count -1;
lstBox.ScrollIntoView(lstBox.SelectedItem) ;
Run Code Online (Sandbox Code Playgroud)

在您的MainWindow中,这将选择并关注列表中的最后一项!

  • 这个答案应该被接受!@ 0xBADF00D如果是这种情况,你应该做`lstBox.SelectedIndex = 0`;) (4认同)

Mat*_*lak 30

最简单的方法:

if (VisualTreeHelper.GetChildrenCount(listView) > 0)
{
    Border border = (Border)VisualTreeHelper.GetChild(listView, 0);
    ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
    scrollViewer.ScrollToBottom();
}
Run Code Online (Sandbox Code Playgroud)

它始终适用于ListView和ListBox控件.将此代码附加到listView.Items.SourceCollection.CollectionChanged事件,您将拥有完全自动的自动滚动行为.

  • 对于任何想知道如何将CollectionChanged附加到列表框的人:在`InitializeComponent();`之后你必须添加`((INotifyCollectionChanged).Items).CollectionChanged + = YourListboxCollectionChanged;` (3认同)
  • 如果您为`ListBox`使用自定义模板,这可能不起作用,所以要小心. (2认同)

Nov*_*gic 28

请记住,listBox.ScrollIntoView(listBox.Items[listBox.Items.Count - 1]);仅当您没有重复项目时才有效.如果您有相同内容的项目,则向下滚动到第一个查找.

这是我找到的解决方案:

ListBoxAutomationPeer svAutomation = (ListBoxAutomationPeer)ScrollViewerAutomationPeer.CreatePeerForElement(myListBox);

IScrollProvider scrollInterface = (IScrollProvider)svAutomation.GetPattern(PatternInterface.Scroll);
System.Windows.Automation.ScrollAmount scrollVertical = System.Windows.Automation.ScrollAmount.LargeIncrement;
System.Windows.Automation.ScrollAmount scrollHorizontal = System.Windows.Automation.ScrollAmount.NoAmount;
//If the vertical scroller is not available, the operation cannot be performed, which will raise an exception. 
if ( scrollInterface.VerticallyScrollable )
    scrollInterface.Scroll(scrollHorizontal, scrollVertical);
Run Code Online (Sandbox Code Playgroud)

  • 太棒了,谢谢.其他人的FYI:必须将这些引用添加到您的项目中:UIAutomationProvider和UIAutomationTypes (2认同)

小智 20

最好的解决方案是在ListBox控件中使用ItemCollection对象,此集合是专门为内容查看器设计的.它有一个预定义的方法来选择最后一项并保持光标位置参考....

myListBox.Items.MoveCurrentToLast();
myListBox.ScrollIntoView(myListBox.Items.CurrentItem);
Run Code Online (Sandbox Code Playgroud)


Joh*_*ien 6

这里的答案都没有达到我的需要。因此,我编写了自己的行为,自动滚动项目控件,并在用户向上滚动时暂停自动滚动,并在用户向下滚动到底部时恢复自动滚动。

/// <summary>
/// This will auto scroll a list view to the bottom as items are added.
/// Automatically suspends if the user scrolls up, and recommences when
/// the user scrolls to the end.
/// </summary>
/// <example>
///     <ListView sf:AutoScrollToBottomBehavior="{Binding viewModelAutoScrollFlag}" />
/// </example>
public class AutoScrollToBottomBehavior
{
  /// <summary>
  /// Enumerated type to keep track of the current auto scroll status
  /// </summary>
  public enum StatusType
  {
    NotAutoScrollingToBottom,
    AutoScrollingToBottom,
    AutoScrollingToBottomButSuppressed
  }

  public static StatusType GetAutoScrollToBottomStatus(DependencyObject obj)
  {
    return (StatusType)obj.GetValue(AutoScrollToBottomStatusProperty);
  }

  public static void SetAutoScrollToBottomStatus(DependencyObject obj, StatusType value)
  {
    obj.SetValue(AutoScrollToBottomStatusProperty, value);
  }

  // Using a DependencyProperty as the backing store for AutoScrollToBottomStatus.  This enables animation, styling, binding, etc...
  public static readonly DependencyProperty AutoScrollToBottomStatusProperty =
      DependencyProperty.RegisterAttached(
        "AutoScrollToBottomStatus",
        typeof(StatusType),
        typeof(AutoScrollToBottomBehavior),
        new PropertyMetadata(StatusType.NotAutoScrollingToBottom, (s, e) =>
        {
          if (s is DependencyObject viewer && e.NewValue is StatusType autoScrollToBottomStatus)
          {
            // Set the AutoScrollToBottom property to mirror this one

            bool? autoScrollToBottom = autoScrollToBottomStatus switch
            {
              StatusType.AutoScrollingToBottom => true,
              StatusType.NotAutoScrollingToBottom => false,
              StatusType.AutoScrollingToBottomButSuppressed => false,
              _ => null
            };

            if (autoScrollToBottom.HasValue)
            {
              SetAutoScrollToBottom(viewer, autoScrollToBottom.Value);
            }

            // Only hook/unhook for cases below, not when suspended
            switch(autoScrollToBottomStatus)
            {
              case StatusType.AutoScrollingToBottom:
                HookViewer(viewer);
                break;
              case StatusType.NotAutoScrollingToBottom:
                UnhookViewer(viewer);
                break;
            }
          }
        }));


  public static bool GetAutoScrollToBottom(DependencyObject obj)
  {
    return (bool)obj.GetValue(AutoScrollToBottomProperty);
  }

  public static void SetAutoScrollToBottom(DependencyObject obj, bool value)
  {
    obj.SetValue(AutoScrollToBottomProperty, value);
  }

  // Using a DependencyProperty as the backing store for AutoScrollToBottom.  This enables animation, styling, binding, etc...
  public static readonly DependencyProperty AutoScrollToBottomProperty =
      DependencyProperty.RegisterAttached(
        "AutoScrollToBottom",
        typeof(bool),
        typeof(AutoScrollToBottomBehavior),
        new FrameworkPropertyMetadata(false,  FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) =>
        {
          if (s is DependencyObject viewer && e.NewValue is bool autoScrollToBottom)
          {
            // Set the AutoScrollToBottomStatus property to mirror this one
            if (autoScrollToBottom)
            {
              SetAutoScrollToBottomStatus(viewer, StatusType.AutoScrollingToBottom);
            }
            else if (GetAutoScrollToBottomStatus(viewer) == StatusType.AutoScrollingToBottom)
            {
              SetAutoScrollToBottomStatus(viewer, StatusType.NotAutoScrollingToBottom);
            }

            // No change if autoScrollToBottom = false && viewer.AutoScrollToBottomStatus = AutoScrollToBottomStatusType.AutoScrollingToBottomButSuppressed;
          }
        }));


  private static Action GetUnhookAction(DependencyObject obj)
  {
    return (Action)obj.GetValue(UnhookActionProperty);
  }

  private static void SetUnhookAction(DependencyObject obj, Action value)
  {
    obj.SetValue(UnhookActionProperty, value);
  }

  // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
  private static readonly DependencyProperty UnhookActionProperty =
      DependencyProperty.RegisterAttached("UnhookAction", typeof(Action), typeof(AutoScrollToBottomBehavior), new PropertyMetadata(null));

  private static void ItemsControl_Loaded(object sender, RoutedEventArgs e)
  {
    if (sender is ItemsControl itemsControl)
    {
      itemsControl.Loaded -= ItemsControl_Loaded;
      HookViewer(itemsControl);
    }
  }

  private static void HookViewer(DependencyObject viewer)
  {
    if (viewer is ItemsControl itemsControl)
    {
      // If this is triggered the xaml setup then the control won't be loaded yet,
      // and so won't have a visual tree which we need to get the scrollviewer,
      // so defer this hooking until the items control is loaded.
      if (!itemsControl.IsLoaded)
      {
        itemsControl.Loaded += ItemsControl_Loaded;
        return;
      }

      if (FindScrollViewer(viewer) is ScrollViewer scrollViewer)
      {
        scrollViewer.ScrollToBottom();

        // Scroll to bottom when the item count changes
        NotifyCollectionChangedEventHandler itemsCollectionChangedHandler = (s, e) =>
        {
          if (GetAutoScrollToBottom(viewer))
          {
            scrollViewer.ScrollToBottom();
          }
        };
        ((INotifyCollectionChanged)itemsControl.Items).CollectionChanged += itemsCollectionChangedHandler;


        ScrollChangedEventHandler scrollChangedEventHandler = (s, e) =>
        {
          bool userScrolledToBottom = (e.VerticalOffset + e.ViewportHeight) > (e.ExtentHeight - 1.0);
          bool userScrolledUp = e.VerticalChange < 0;

          // Check if auto scrolling should be suppressed
          if (userScrolledUp && !userScrolledToBottom)
          {
            if (GetAutoScrollToBottomStatus(viewer) == StatusType.AutoScrollingToBottom)
            {
              SetAutoScrollToBottomStatus(viewer, StatusType.AutoScrollingToBottomButSuppressed);
            }
          }

          // Check if auto scrolling should be unsuppressed
          if (userScrolledToBottom)
          {
            if (GetAutoScrollToBottomStatus(viewer) == StatusType.AutoScrollingToBottomButSuppressed)
            {
              SetAutoScrollToBottomStatus(viewer, StatusType.AutoScrollingToBottom);
            }
          }
        };

        scrollViewer.ScrollChanged += scrollChangedEventHandler;

        Action unhookAction = () =>
        {
          ((INotifyCollectionChanged)itemsControl.Items).CollectionChanged -= itemsCollectionChangedHandler;
          scrollViewer.ScrollChanged -= scrollChangedEventHandler;
        };

        SetUnhookAction(viewer, unhookAction);
      }
    }
  }

  /// <summary>
  /// Unsubscribes the event listeners on the ItemsControl and ScrollViewer
  /// </summary>
  /// <param name="viewer"></param>
  private static void UnhookViewer(DependencyObject viewer)
  {
    var unhookAction = GetUnhookAction(viewer);
    SetUnhookAction(viewer, null);
    unhookAction?.Invoke();
  }

  /// <summary>
  /// A recursive function that drills down a visual tree until a ScrollViewer is found.
  /// </summary>
  /// <param name="viewer"></param>
  /// <returns></returns>
  private static ScrollViewer FindScrollViewer(DependencyObject viewer)
  {
    if (viewer is ScrollViewer scrollViewer)
      return scrollViewer;

    return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(viewer))
      .Select(i => FindScrollViewer(VisualTreeHelper.GetChild(viewer, i)))
      .Where(child => child != null)
      .FirstOrDefault();
  }
}
Run Code Online (Sandbox Code Playgroud)


JOK*_*OKe 5

listBox.ScrollIntoView(listBox.Items[listBox.Items.Count - 1]);


Scr*_*og1 5

与到目前为止介绍的方法略有不同。

您可以使用该ScrollViewer ScrollChanged事件,并观察ScrollViewer变得越来越大的内容。

private void ListBox_OnLoaded(object sender, RoutedEventArgs e)
{
    var listBox = (ListBox) sender;

    var scrollViewer = FindScrollViewer(listBox);

    if (scrollViewer != null)
    {
        scrollViewer.ScrollChanged += (o, args) =>
        {
            if (args.ExtentHeightChange > 0)
                scrollViewer.ScrollToBottom();
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

这避免了与ListBox ItemsSource更改绑定的某些问题。

ScrollViewer也可以不使该假设发现ListBox正在使用默认控件模板。

// Search for ScrollViewer, breadth-first
private static ScrollViewer FindScrollViewer(DependencyObject root)
{
    var queue = new Queue<DependencyObject>(new[] {root});

    do
    {
        var item = queue.Dequeue();

        if (item is ScrollViewer)
            return (ScrollViewer) item;

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(item); i++)
            queue.Enqueue(VisualTreeHelper.GetChild(item, i));
    } while (queue.Count > 0);

    return null;
}
Run Code Online (Sandbox Code Playgroud)

然后将此附加到ListBox Loaded事件:

<ListBox Loaded="ListBox_OnLoaded" />
Run Code Online (Sandbox Code Playgroud)

可以很容易地将其修改为附加属性,以使其更通用。


Anv*_*aka 0

您可以尝试ListBox.ScrollIntoView()方法,尽管在某些情况下存在一些问题......

以下是 Tamir Khason 的示例:Auto scroll ListBox in WPF