So I discovered Joymon's solution for addressing the attached events after posting my question, and used his RoutedEventTrigger class, combined with the code sensing the end-of-scroll condition in my view model.
For the record, here are the pieces of the solution:
xmlns:wpfCommon="clr-namespace:WPFCommon;assembly=WPFCommon"
...
<DataGrid x:Name="SCPLog">
<i:Interaction.Triggers>
<wpfCommon:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<cal:ActionMessage MethodName="DoScroll">
<cal:Parameter Value="$eventargs" />
</cal:ActionMessage>
</wpfCommon:RoutedEventTrigger>
</i:Interaction.Triggers>
</DataGrid>
And a variation on Joymon's RoutedEventTrigger, which I placed in my own WPFCommon library:
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
public RoutedEvent RoutedEvent { get; set; }
protected override void OnAttached()
{
var behavior = base.AssociatedObject as Behavior;
var associatedElement = base.AssociatedObject as FrameworkElement;
if (behavior != null)
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
if (associatedElement == null)
throw new ArgumentException("Routed Event trigger can only be associated to framework elements");
if (RoutedEvent != null)
associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
}
void OnRoutedEvent(object sender, RoutedEventArgs args) { base.OnEvent(args); }
protected override string GetEventName() { return RoutedEvent.Name; }
}
With the end-of-scroll detection in the view model:
public void DoScroll(ScrollChangedEventArgs e)
{
var scrollViewer = e.OriginalSource as ScrollViewer;
if (scrollViewer != null && // Do we have a scroll bar?
scrollViewer.ScrollableHeight > 0 && // Avoid firing the event on an empty list.
scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight && // Are we at the end of the scrollbar?
{
// Do your end-of-scroll code here...
}
}
If someone knows a better way to handle the end-of-scroll event, e.g. doing it in XAML, I'd love to hear it.