Is there an event that is raised when a child is scrolled into view and gives an indication of what child was realized?
Of course there is the ScrollChanged event, but it does not provide me with any indication of what element was scrolled into view.
Edit :
I've tried hooking up to the ScrollViewer's RequestBringIntoView Event, but it is never reached. Alternatively I also tried the same on the StackPanel containing the items as such:
XAML :
<ScrollViewer RequestBringIntoView="ScrollViewer_RequestBringIntoView" >
<StackPanel RequestBringIntoView="StackPanel_RequestBringIntoView">
<Button Content="1" Height="20"/>
<Button Content="2" Height="20"/>
<Button Content="3" Height="20"/>
<Button Content="4" Height="20"/>
<Button Content="5" Height="20"/>
<Button Content="6" Height="20"/>
<Button Content="7" Height="20"/>
<Button Content="8" Height="20"/>
<Button Content="9" Height="20"/>
<Button Content="10" Height="20"/>
<Button Content="11" Height="20"/>
<Button Content="12" Height="20"/>
<Button Content="13" Height="20"/>
<Button Content="14" Height="20"/>
<Button Content="15" Height="20"/>
<Button Content="16" Height="20"/>
<Button Content="17" Height="20"/>
<Button Content="18" Height="20"/>
<Button Content="19" Height="20"/>
<Button Content="20" Height="20"/>
<Button Content="21" Height="20"/>
<Button Content="22" Height="20"/>
<Button Content="23" Height="20"/>
<Button Content="24" Height="20"/>
</StackPanel>
</ScrollViewer>
They are never reached. As I understand it, the ScrollViewer calls BringIntoView on it's encapsulated child elements and they raise the RequestBringIntoView event, which I would expect to propagate up the visual tree. I guess the ScrollViewer handles that event internally. So I end up with the same problem of how to get notified when it's child is brought into view. I could hook each of them up, or maybe an ItemsControl would do that for me?
I think you should look at this article which gives a way of telling if a control is visible to the viewer.
If you were to hook up a call to that custom method in your ScrollChanged handler, thus having a checked every time you scrolled, I think that would do the trick. I'll try this out myself....
Edit: It works! Here's the method:
private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
if (!element.IsVisible)
return false;
Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}
And my simple code call:
private void Scroll_Changed(object sender, ScrollChangedEventArgs e)
{
Object o = sender;
bool elementIsVisible = false;
foreach (FrameworkElement child in this.stackPanel1.Children)
{
if (child != null)
{
elementIsVisible = this.IsUserVisible(child, this.scroller);
if (elementIsVisible)
{
// Your logic
}
}
}
}
Edit: I took a look through the source code of the ScrollViewer from the link that dev hedgehog posted and found this interesting private function:
// Returns true only if element is partly visible in the current viewport
private bool IsInViewport(ScrollContentPresenter scp, DependencyObject element)
{
Rect viewPortRect = KeyboardNavigation.GetRectangle(scp);
Rect elementRect = KeyboardNavigation.GetRectangle(element);
return viewPortRect.IntersectsWith(elementRect);
}
This obviously suggests that even the ScrollViewer itself is interested in knowing what's visible and, as I expected, essentially performs the same kind of calculation as in that helper method. It might be worthwhile to download this code and see who calls this method, where, and why.
Edit: Looks like its called by OnKeyDown() and used to determine focus behavior, and that's it. Interesting....