views:

780

answers:

1

Hello, 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 the ListBox to scroll to the right so that the new item is viewable.

The ListBox is defined in a DataTemplate, so I am unable to access the ListBox by name in my code behind file.

How can I get a ListBox to always scroll to show a latest added item?

I would like a way to know when the ListBox has a new item added to it, but I do not see an event that does this.

Thanks!

+3  A: 

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:

public class ListBoxBehavior
{
    static 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 += new RoutedEventHandler(ListBox_Loaded);
            listBox.Unloaded += new RoutedEventHandler(ListBox_Unloaded);
        }
        else
        {
            listBox.Loaded -= ListBox_Loaded;
            listBox.Unloaded -= ListBox_Unloaded;
            if (Associations.ContainsKey(listBox))
                Associations[listBox].Dispose();
        }
    }

    static void ListBox_Unloaded(object sender, RoutedEventArgs e)
    {
        var listBox = (ListBox)sender;
        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
    {
        public ListBox listBox { get; set; }
        public INotifyCollectionChanged incc { get; set; }

        public Capture(ListBox listBox)
        {
            this.listBox = listBox;
            incc = listBox.ItemsSource as INotifyCollectionChanged;
            incc.CollectionChanged += 
                new NotifyCollectionChangedEventHandler(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()
        {
            incc.CollectionChanged -= incc_CollectionChanged;
        }
    }
}

Usage:

<ListBox ItemsSource="{Binding SourceCollection}" 
         lb:ListBoxBehavior.ScrollOnNewItem="true"/>
Aviad P.
thanks, I added this code to my project and it worked as is! I really appreciate the quick and accurate reply.I don't quite understand what is happening on the line:var incc = listBox.Items as INotifyCollectionChanged;How can listBox Items be cast to INotifyCollectionChanged? Where can I learn more about creating attached properties in general?
Taylor
update: the code above works most of the time for me - at times listbox items are added and the listbox does not scroll.
Taylor
I think I meant to write listBox.ItemsSource... I'll try that. Btw it works for me every time, perhaps it's a problem with focus. Does the selection change work always?
Aviad P.