How can I have a ListBox auto-scroll when a new item is added?

后端 未结 12 2228
走了就别回头了
走了就别回头了 2020-11-28 20:34

I have a WPF ListBox that is set to scroll horizontally. The ItemsSource is bound to an ObservableCollection in my ViewModel class. Every time a new item is added, I want th

相关标签:
12条回答
  • 2020-11-28 20:58

    This works for me:

    DirectoryInfo di = new DirectoryInfo(folderBrowserDialog1.SelectedPath);
    foreach (var fi in di.GetFiles("*", SearchOption.AllDirectories))
    {
        int count = Convert.ToInt32(listBox1.Items.Count); // counts every listbox entry
        listBox1.Items.Add(count + " - " + fi.Name); // display entrys
        listBox1.TopIndex = count; // scroll to the last entry
    }
    
    0 讨论(0)
  • 2020-11-28 20:59

    This is the solution I use that works, might help someone else;

     statusWindow.SelectedIndex = statusWindow.Items.Count - 1;
     statusWindow.UpdateLayout();
     statusWindow.ScrollIntoView(statusWindow.SelectedItem);
     statusWindow.UpdateLayout();
    
    0 讨论(0)
  • 2020-11-28 21:01

    You can extend the behavior of the ListBox by using attached properties. In your case I would define an attached property called ScrollOnNewItem that when set to true hooks into the INotifyCollectionChanged events of the list box items source and upon detecting a new item, scrolls the list box to it.

    Example:

    class ListBoxBehavior
    {
        static readonly Dictionary<ListBox, Capture> Associations =
               new Dictionary<ListBox, Capture>();
    
        public static bool GetScrollOnNewItem(DependencyObject obj)
        {
            return (bool)obj.GetValue(ScrollOnNewItemProperty);
        }
    
        public static void SetScrollOnNewItem(DependencyObject obj, bool value)
        {
            obj.SetValue(ScrollOnNewItemProperty, value);
        }
    
        public static readonly DependencyProperty ScrollOnNewItemProperty =
            DependencyProperty.RegisterAttached(
                "ScrollOnNewItem",
                typeof(bool),
                typeof(ListBoxBehavior),
                new UIPropertyMetadata(false, OnScrollOnNewItemChanged));
    
        public static void OnScrollOnNewItemChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var listBox = d as ListBox;
            if (listBox == null) return;
            bool oldValue = (bool)e.OldValue, newValue = (bool)e.NewValue;
            if (newValue == oldValue) return;
            if (newValue)
            {
                listBox.Loaded += ListBox_Loaded;
                listBox.Unloaded += ListBox_Unloaded;
                var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"];
                itemsSourcePropertyDescriptor.AddValueChanged(listBox, ListBox_ItemsSourceChanged);
            }
            else
            {
                listBox.Loaded -= ListBox_Loaded;
                listBox.Unloaded -= ListBox_Unloaded;
                if (Associations.ContainsKey(listBox))
                    Associations[listBox].Dispose();
                var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"];
                itemsSourcePropertyDescriptor.RemoveValueChanged(listBox, ListBox_ItemsSourceChanged);
            }
        }
    
        private static void ListBox_ItemsSourceChanged(object sender, EventArgs e)
        {
            var listBox = (ListBox)sender;
            if (Associations.ContainsKey(listBox))
                Associations[listBox].Dispose();
            Associations[listBox] = new Capture(listBox);
        }
    
        static void ListBox_Unloaded(object sender, RoutedEventArgs e)
        {
            var listBox = (ListBox)sender;
            if (Associations.ContainsKey(listBox))
                Associations[listBox].Dispose();
            listBox.Unloaded -= ListBox_Unloaded;
        }
    
        static void ListBox_Loaded(object sender, RoutedEventArgs e)
        {
            var listBox = (ListBox)sender;
            var incc = listBox.Items as INotifyCollectionChanged;
            if (incc == null) return;
            listBox.Loaded -= ListBox_Loaded;
            Associations[listBox] = new Capture(listBox);
        }
    
        class Capture : IDisposable
        {
            private readonly ListBox listBox;
            private readonly INotifyCollectionChanged incc;
    
            public Capture(ListBox listBox)
            {
                this.listBox = listBox;
                incc = listBox.ItemsSource as INotifyCollectionChanged;
                if (incc != null)
                {
                    incc.CollectionChanged += incc_CollectionChanged;
                }
            }
    
            void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    listBox.ScrollIntoView(e.NewItems[0]);
                    listBox.SelectedItem = e.NewItems[0];
                }
            }
    
            public void Dispose()
            {
                if (incc != null)
                    incc.CollectionChanged -= incc_CollectionChanged;
            }
        }
    }
    

    Usage:

    <ListBox ItemsSource="{Binding SourceCollection}" 
             lb:ListBoxBehavior.ScrollOnNewItem="true"/>
    

    UPDATE As per Andrej's suggestion in the comments below, I added hooks to detect a change in the ItemsSource of the ListBox.

    0 讨论(0)
  • 2020-11-28 21:06

    I use this solution: http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/.

    It works even if you bind listbox's ItemsSource to an ObservableCollection that is manipulated in a non-UI thread.

    0 讨论(0)
  • 2020-11-28 21:06

    So what i read in this topcs is a little bit complex for a simple action.

    So I subscribed to scrollchanged event and then I used this code:

    private void TelnetListBox_OnScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var scrollViewer = ((ScrollViewer)e.OriginalSource);
            scrollViewer.ScrollToEnd();
    
        }
    

    Bonus:

    After it I made a checkbox where I could set when I want use the autoscroll function and I relaized I forgot some times uncheck the listbox if I saw some interesting information for me. So I decided I would like to create a intelligent autoscrolled listbox what react to my mouse action.

    private void TelnetListBox_OnScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var scrollViewer = ((ScrollViewer)e.OriginalSource);
            scrollViewer.ScrollToEnd();
            if (AutoScrollCheckBox.IsChecked != null && (bool)AutoScrollCheckBox.IsChecked)
                scrollViewer.ScrollToEnd();
    
            if (_isDownMouseMovement)
            {
                var verticalOffsetValue = scrollViewer.VerticalOffset;
                var maxVerticalOffsetValue = scrollViewer.ExtentHeight - scrollViewer.ViewportHeight;
    
                if (maxVerticalOffsetValue < 0 || verticalOffsetValue == maxVerticalOffsetValue)
                {
                    // Scrolled to bottom
    
                    AutoScrollCheckBox.IsChecked = true;
                    _isDownMouseMovement = false;
    
                }
                else if (verticalOffsetValue == 0)
                {
    
    
                }
    
            }
        }
    
    
    
        private bool _isDownMouseMovement = false;
    
        private void TelnetListBox_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
    
            if (e.Delta > 0)
            {
                _isDownMouseMovement = false;
                AutoScrollCheckBox.IsChecked = false;
            }
            if (e.Delta < 0)
            {
                _isDownMouseMovement = true;
            } 
        }
    

    When I scolled to botton the checkbox checked true and stay my view on bottom if I scroulled up with mouse wheel the checkox will be unchecked and you can explorer you listbox.

    0 讨论(0)
  • 2020-11-28 21:13

    I found a much simpler way which helped me with a similar problem, just a couple of lines of code behind, no need to create custom Behaviors. Check my answer to this question (and follow the link within):

    wpf(C#) DataGrid ScrollIntoView - how to scroll to the first row that is not shown?

    It works for ListBox, ListView and DataGrid.

    0 讨论(0)
提交回复
热议问题