首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >WPF ListBox自动滚动启动和停止行为

WPF ListBox自动滚动启动和停止行为
EN

Stack Overflow用户
提问于 2014-09-12 03:54:44
回答 2查看 2.2K关注 0票数 2

我一直试图通过以下方式改进WPF ListBox控件的行为:当添加新项时,下面的ListBox会自动滚动到底部。它使用所示的ScrollToBottom函数执行此操作。使用显示的预览事件,即使添加了更多项,如果用户单击某项,它也将停止滚动。(让它继续滚动将是令人讨厌的!)如果用户用鼠标或车轮手动滚动,那么它将停止以同样的方式滚动。

现在,我有一个按钮,在下面的代码,开始自动滚动。

我的问题是:如果用户将列表框一直滚动到底部,或者使用鼠标轮或键盘进行等效操作,那么如何才能开始自动滚动。这就是我以前的Borland列表盒的工作原理。

代码语言:javascript
运行
复制
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;

// Note requires .NET framework 4.5
namespace MMP
{
  public partial class MainWindow : Window
  {
    public ObservableCollection<String> data { get; set; }

    public MainWindow()
    {
      InitializeComponent();
      data = new ObservableCollection<String>();
      DataContext = this;
      BeginAddingItems();
    }

    private async void BeginAddingItems()
    {
      await Task.Factory.StartNew(() =>
      {
        for (int i = 0; i < Int32.MaxValue; ++i)
        {
          if (i > 20) 
            Thread.Sleep(1000);
          AddToList("Added " + i.ToString());
        }
      });
    }

    void AddToList(String item)
    {
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
            new Action(() => { data.Add(item); ScrollToBottom(); }));
    }

    bool autoScroll = true;
    public void ScrollToBottom()
    {
      if (!autoScroll)
        return;
      if (listbox.Items.Count > 0)
        listbox.ScrollIntoView(listbox.Items[listbox.Items.Count - 1]);
    }

    private void listbox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
      autoScroll = false;
      Console.WriteLine("PreviewMouseDown: setting autoScroll to false");
    }

    private void listbox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
      Console.WriteLine("PreviewMouseWheel: setting autoScroll to false");
      autoScroll = false;
    }

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
      ScrollToBottom(); // Catch up with the current last item.
      Console.WriteLine("startButton_Click: setting autoScroll to true");
      autoScroll = true;
    }

    private void listbox_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
      // Can this be useful?
    }
  }
}



<Window x:Class="MMP.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Test Scrolling"
        FontFamily="Verdana"
        Width="400" Height="250"
        WindowStartupLocation="CenterScreen">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <ListBox x:Name="listbox" Grid.Row="0" 
             PreviewMouseWheel="listbox_PreviewMouseWheel" 
             PreviewMouseDown="listbox_PreviewMouseDown" 
             ItemsSource="{Binding data}" ScrollViewer.ScrollChanged="listbox_ScrollChanged" 
             >
    </ListBox>
    <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right">
      <Button x:Name="startButton" Click="startButton_Click" MinWidth="80" >Auto Scroll</Button>
    </StackPanel>
  </Grid>
</Window>
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-09-15 19:53:15

所需的列表框行为是使用以下代码实现的,这要感谢Roel提供了上面的初始Behavior<>框架。这是一个包含行为代码的示例项目,以及一个可以用来测试交互性的最小WPF窗口。

测试窗口包含一个ListBox,通过后台任务异步添加项。该行为的要点如下:

  1. 列表框自动滚动以显示异步添加的新项。
  2. 用户与列表框的交互停止自动滚动- AKA讨厌的行为。
  3. 一旦完成交互,为了继续自动滚动,用户将滚动条拖到底部,然后放手,或者使用鼠标轮或键盘进行同样的操作。这表示用户希望自动滚动恢复。

AutoScrolBehavior.cs:

代码语言:javascript
运行
复制
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace BehaviorTest.Code
{
  // List box automatically scrolls to show new items as they are added asynchronously.
  // A user interaction with the listbox stops automatic scrolling - AKA obnoxious behavior.
  // Once finished interacting, to continue automatic scrolling, drag the scroll bar to 
  // the bottom and let go, or use the mouse wheel or keyboard to do the same. 
  // This indicates that the user wants automatic scrolling to resume.

  public class AutoScrollBehavior : Behavior<ListBox>
  {
    private ScrollViewer scrollViewer;
    private bool autoScroll = true;
    private bool justWheeled = false;
    private bool userInteracting = false;
    protected override void OnAttached()
    {
      AssociatedObject.Loaded += AssociatedObjectOnLoaded;
      AssociatedObject.Unloaded += AssociatedObjectOnUnloaded;
    }

    private void AssociatedObjectOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
    {
      if (scrollViewer != null)
      {
        scrollViewer.ScrollChanged -= ScrollViewerOnScrollChanged;
      }
      AssociatedObject.SelectionChanged -= AssociatedObjectOnSelectionChanged;
      AssociatedObject.ItemContainerGenerator.ItemsChanged -= ItemContainerGeneratorItemsChanged;
      AssociatedObject.GotMouseCapture -= AssociatedObject_GotMouseCapture;
      AssociatedObject.LostMouseCapture -= AssociatedObject_LostMouseCapture;
      AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;

      scrollViewer = null;
    }

    private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
      scrollViewer = GetScrollViewer(AssociatedObject);
      if (scrollViewer != null)
      {
        scrollViewer.ScrollChanged += ScrollViewerOnScrollChanged;

        AssociatedObject.SelectionChanged += AssociatedObjectOnSelectionChanged;
        AssociatedObject.ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorItemsChanged;
        AssociatedObject.GotMouseCapture += AssociatedObject_GotMouseCapture;
        AssociatedObject.LostMouseCapture += AssociatedObject_LostMouseCapture;
        AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
      }
    }

    private static ScrollViewer GetScrollViewer(DependencyObject root)
    {
      int childCount = VisualTreeHelper.GetChildrenCount(root);
      for (int i = 0; i < childCount; ++i)
      {
        DependencyObject child = VisualTreeHelper.GetChild(root, i);
        ScrollViewer sv = child as ScrollViewer;
        if (sv != null)
          return sv;

        return GetScrollViewer(child);
      }
      return null;
    }

    void AssociatedObject_GotMouseCapture(object sender, System.Windows.Input.MouseEventArgs e)
    {
      // User is actively interacting with listbox. Do not allow automatic scrolling to interfere with user experience.
      userInteracting = true;
      autoScroll = false;
    }

    void AssociatedObject_LostMouseCapture(object sender, System.Windows.Input.MouseEventArgs e)
    {
      // User is done interacting with control.
      userInteracting = false;
    }

    private void ScrollViewerOnScrollChanged(object sender, ScrollChangedEventArgs e)
    {
      // diff is exactly zero if the last item in the list is visible. This can occur because of scroll-bar drag, mouse-wheel, or keyboard event.
      double diff = (scrollViewer.VerticalOffset - (scrollViewer.ExtentHeight - scrollViewer.ViewportHeight));

      // User just wheeled; this event is called immediately afterwards.
      if (justWheeled && diff != 0.0)
      {
        justWheeled = false;
        autoScroll = false;
        return;
      }

      if (diff == 0.0)
      {
        // then assume user has finished with interaction and has indicated through this action that scrolling should continue automatically.
        autoScroll = true;
      }
    }

    private void ItemContainerGeneratorItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
    {
      if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Reset)
      { 
        // An item was added to the listbox, or listbox was cleared.
        if (autoScroll && !userInteracting)
        {
          // If automatic scrolling is turned on, scroll to the bottom to bring new item into view.
          // Do not do this if the user is actively interacting with the listbox.
          scrollViewer.ScrollToBottom();
        }
      }
    }

    private void AssociatedObjectOnSelectionChanged(object sender, SelectionChangedEventArgs selectionChangedEventArgs)
    {
      // User selected (clicked) an item, or used the keyboard to select a different item. 
      // Turn off automatic scrolling.
      autoScroll = false;
    }

    void AssociatedObject_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
    {
      // User wheeled the mouse. 
      // Cannot detect whether scroll viewer right at the bottom, because the scroll event has not occurred at this point.
      // Same for bubbling event.
      // Just indicated that the user mouse-wheeled, and that the scroll viewer should decide whether or not to stop autoscrolling.
      justWheeled = true;
    }
  }
}

MainWindow.xaml.cs:

代码语言:javascript
运行
复制
using BehaviorTest.Code;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Threading;

namespace BehaviorTest
{
  public partial class MainWindow : Window
  {
    public ObservableCollection<String> data { get; set; }
    public MainWindow()
    {
      InitializeComponent();
      data = new ObservableCollection<String>();
      DataContext = this;
      Interaction.GetBehaviors(listbox).Add(new AutoScrollBehavior());
      BeginAddingItems();
    }
    private async void BeginAddingItems()
    {
      List<Task> tasks = new List<Task>();

      await Task.Factory.StartNew(() =>
      {
        for (int i = 0; i < Int32.MaxValue; ++i)
        {
          AddToList("Added Slowly: " + i.ToString());
          Thread.Sleep(2000);
          if (i % 3 == 0)
          {
            for (int j = 0; j < 5; ++j)
            {
              AddToList("Added Quickly: " + j.ToString());
              Thread.Sleep(200);
            }
          }
        }
      });
    }

    void AddToList(String item)
    {
      if (Application.Current == null)
        return; // Application is shutting down.
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
          new Action(() => { data.Add(item); }));
    }

    private void clearButton_Click(object sender, RoutedEventArgs e)
    {
      data.Clear();
    }

    private void listbox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
      MessageBox.Show("Launch a modal dialog. Items are still added to the list in the background.");
    }
  }
}

MainWindow.xaml.cs:

代码语言:javascript
运行
复制
<Window x:Class="BehaviorTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Test Scrolling"
        FontFamily="Verdana"
        Width="400" Height="250"
        WindowStartupLocation="CenterScreen">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <ListBox x:Name="listbox" Grid.Row="0" 
             ItemsSource="{Binding data}"
             MouseDoubleClick="listbox_MouseDoubleClick" >
    </ListBox>
    <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right">
      <Button x:Name="startButton" Click="clearButton_Click" MinWidth="80" >Clear</Button>
    </StackPanel>
  </Grid>
</Window>
票数 1
EN

Stack Overflow用户

发布于 2014-09-12 05:41:10

您可以尝试创建一个为您这样做的Blend Behavior。这是一个小小的开端:

代码语言:javascript
运行
复制
public class AutoScrollBehavior:Behavior<ListBox> 
{
    private ScrollViewer scrollViewer;
    private bool autoScroll = true;
    protected override void OnAttached() 
    {
        AssociatedObject.Loaded += AssociatedObjectOnLoaded;
        AssociatedObject.Unloaded += AssociatedObjectOnUnloaded;      
    }

    private void AssociatedObjectOnUnloaded(object sender, RoutedEventArgs routedEventArgs) 
    {
        AssociatedObject.SelectionChanged -= AssociatedObjectOnSelectionChanged;
        AssociatedObject.ItemContainerGenerator.ItemsChanged -= ItemContainerGeneratorItemsChanged;

        scrollViewer = null;
    }

    private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs) 
    {
        scrollViewer = GetScrollViewer(AssociatedObject);
        if(scrollViewer != null) 
        {
            scrollViewer.ScrollChanged += ScrollViewerOnScrollChanged;

            AssociatedObject.SelectionChanged += AssociatedObjectOnSelectionChanged;
            AssociatedObject.ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorItemsChanged;
        }
    }

    private void ScrollViewerOnScrollChanged(object sender, ScrollChangedEventArgs e) {
        if (e.VerticalOffset == e.ExtentHeight-e.ViewportHeight) {
            autoScroll = true;
        }
    }

    private static ScrollViewer GetScrollViewer(DependencyObject root) 
    {
        int childCount = VisualTreeHelper.GetChildrenCount(root);
        for (int i = 0; i < childCount; i++) 
        {
            DependencyObject child = VisualTreeHelper.GetChild(root, i);
            ScrollViewer sv = child as ScrollViewer;
            if (sv != null)
                return sv;

            return GetScrollViewer(child);
        }

        return null;
    }

    private void ItemContainerGeneratorItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e) 
    {
        if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Reset) {
            if (autoScroll) {
                scrollViewer.ScrollToBottom();

            }
        }
    }

    private void AssociatedObjectOnSelectionChanged(object sender, SelectionChangedEventArgs selectionChangedEventArgs) 
    {
        autoScroll = false;
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25800470

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档