I have a XAML view with a list box:
<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}" SelectedItem="{Binding SelectedFoo, Mode=TwoWay}" ScrollSelectedItem="{Binding SelectedFoo}"> <!-- data templates, etc. --> </control:ListBoxScroll>
The selected item is bound to a property in my view. When the user selects an item in the list box my SelectedFoo property in the view model gets updated. When I set the SelectedFoo property in my view model then the correct item is selected in the list box.
The problem is that if the SelectedFoo that is set in code is not currently visible I need to additionally call ScrollIntoView
on the list box. Since my ListBox is inside a view and my logic is inside my view model ... I couldn't find a convenient way to do it. So I extended ListBoxScroll:
class ListBoxScroll : ListBox { public static readonly DependencyProperty ScrollSelectedItemProperty = DependencyProperty.Register( "ScrollSelectedItem", typeof(object), typeof(ListBoxScroll), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(onScrollSelectedChanged))); public object ScrollSelectedItem { get { return (object)GetValue(ScrollSelectedItemProperty); } set { SetValue(ScrollSelectedItemProperty, value); } } private static void onScrollSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var listbox = d as ListBoxScroll; listbox.ScrollIntoView(e.NewValue); } }
It basically exposes a new dependency property ScrollSelectedItem
which I bind to the SelectedFoo
property on my view model. I then hook into the property changed callback of the dependent property and scroll the newly selected item into view.
Does anyone else know of an easier way to call functions on user controls on a XAML view that is backed by a view model? It's a bit of a run around to:
It would be nice to put the logic right in the ScrollSelectedItem { set {
method but the dependency framework seems to sneak around and manages to work without actually calling it.
Have you tried using Behavior... Here is a ScrollInViewBehavior. I have used it for ListView and DataGrid..... I thinks it should work for ListBox......
You have to add a reference to System.Windows.Interactivity
to use Behavior<T> class
public class ScrollIntoViewForListBox : Behavior<ListBox> { /// <summary> /// When Beahvior is attached /// </summary> protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged; } /// <summary> /// On Selection Changed /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (sender is ListBox) { ListBox listBox = (sender as ListBox); if (listBox .SelectedItem != null) { listBox.Dispatcher.BeginInvoke( (Action) (() => { listBox.UpdateLayout(); if (listBox.SelectedItem != null) listBox.ScrollIntoView( listBox.SelectedItem); })); } } } /// <summary> /// When behavior is detached /// </summary> protected override void OnDetaching() { base.OnDetaching(); this.AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged; } }
Add alias to XAML
as xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
then in your Control
<ListBox ItemsSource="{Binding Path=MyList}" SelectedItem="{Binding Path=MyItem, Mode=TwoWay}" SelectionMode="Single"> <i:Interaction.Behaviors> <Behaviors:ScrollIntoViewForListBox /> </i:Interaction.Behaviors> </ListBox>
Now When ever "MyItem" property is set in ViewModel
the List will be scrolled when changes are refelected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With