In general the best way to achieve this is with a behaviour. The implementation will likely depend on your specific requirements but what I'll provide here is a generic example that shows how to make the view model trigger the ListBox to scroll to a specific item of your choosing.
First of all you need a way of communicating this from the view model to the view, you can't bind directly to events in XAML but you can encapsulate the event in a wrapper and bind to that instead:
public class ListBoxScrollHandler
{
public event Action<object> ScrollEvent;
public void ScrollTo(object item)
{
if (this.ScrollEvent != null)
this.ScrollEvent(item);
}
}
That class contains an event which our behaviour can bind to and a ScrollTo
method that our view model can invoke. For the view let's just create a simple listbox that we'll fill with numbers (actually strings) and a button that will force us to scroll to the element with the content "500":
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="Scroll to 500" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding ScrollCommand}" CommandParameter="500" />
<ListBox Grid.Row="1" ItemsSource="{Binding MyItems}" SelectedItem="{Binding CurrentItem}" ScrollViewer.VerticalScrollBarVisibility="Visible">
<i:Interaction.Behaviors>
<behaviors:ListBoxScrollBehavior ScrollHandler="{Binding ScrollHandler}" />
</i:Interaction.Behaviors>
</ListBox>
</Grid>
As you can see I've implemented this with a Blend behaviour, you can of course do it with a regular attached behaviour if you want but I'm keeping things simple here:
public class ListBoxScrollBehavior : Behavior<ListBox>
{
public ListBoxScrollHandler ScrollHandler
{
get { return (ListBoxScrollHandler)GetValue(ScrollHandlerProperty); }
set { SetValue(ScrollHandlerProperty, value); }
}
public static readonly DependencyProperty ScrollHandlerProperty =
DependencyProperty.Register("ScrollHandler", typeof(ListBoxScrollHandler),
typeof(ListBoxScrollBehavior), new PropertyMetadata(null, OnScrollHandlerChanged));
protected override void OnAttached()
{
base.OnAttached();
}
protected override void OnDetaching()
{
base.OnDetaching();
}
private static void OnScrollHandlerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as ListBoxScrollBehavior;
if (behavior == null)
return;
var oldHandler = e.OldValue as ListBoxScrollHandler;
if (oldHandler != null)
oldHandler.ScrollEvent -= behavior.ScrollTo;
var newHandler = e.NewValue as ListBoxScrollHandler;
if (newHandler != null)
newHandler.ScrollEvent += behavior.ScrollTo;
}
public void ScrollTo(object item)
{
this.AssociatedObject.ScrollIntoView(item);
}
}
So our behaviour contains a "ScrollHandler" dependency property that we can bind to our view model and responds by calling the listbox's ScrollIntoView method. After that it's simply a matter of creating a view model that provides this property along with code to initialize the list items and a command handler that responds to the button press and invokes it's scroll handler's ScrollTo method:
public class MainViewModel : ViewModelBase
{
private ObservableCollection<string> _MyItems = new ObservableCollection<string>();
public ObservableCollection<string> MyItems
{
get { return this._MyItems; }
set { this._MyItems = value; RaisePropertyChanged(); }
}
private string _SelectedItem;
public string SelectedItem
{
get { return this._SelectedItem; }
set { this._SelectedItem = value; RaisePropertyChanged(); }
}
public ICommand ScrollCommand { get { return new RelayCommand<string>(OnScroll); } }
private void OnScroll(string item)
{
this.ScrollHandler.ScrollTo(item);
}
private ListBoxScrollHandler _ScrollHandler = new ListBoxScrollHandler();
public ListBoxScrollHandler ScrollHandler
{
get { return this._ScrollHandler;}
set { this._ScrollHandler = value; RaisePropertyChanged(); }
}
public MainViewModel()
{
for (int i = 0; i < 1000; i++)
this.MyItems.Add(i.ToString());
}
}
Run the code, click the button and the listbox will scroll down to the element containing the "500" content. Obviously if you only need a sub-set of this behaviour (e.g. scroll to the currently selected item) then you can modify this behaviour accordingly.