ScrollViewer - Indication of child element scrolle

2020-02-26 14:13发布

问题:

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.

Thanks in advance.

Edit :

Iv'e 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..?

回答1:

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....



回答2:

New Edit: I read your question again and I realized I didn't understand you in first place.

Sorry I thought you mean you wish to be notified what children are inside Viewport of ScrollViewer when you mouse your mouse inside, or set focus betweent first visible or last visible item. That's when RequestBringIntoView comes handy.

Still there are few things which aren't clear to me:

"Is there an event that is raised when a child is scrolled into view which gives an indication of what child was realized ?" - Are you talking about normal panel or VirtualizingStackPanel?

The answer ouflak posted is not bad design at all. It is actually usual WPF.

If you still not happen with our suggestion take a look at source code of ScrollViewer.

http://www.dotnetframework.org/default.aspx/Dotnetfx_Win7_3@5@1/Dotnetfx_Win7_3@5@1/3@5@1/DEVDIV/depot/DevDiv/releases/Orcas/NetFXw7/wpf/src/Framework/System/Windows/Controls/ScrollViewer@cs/2/ScrollViewer@cs

Maybe there you will stumble upon an event you could use.

Old Edit: Is this what you looking for.

http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.requestbringintoview.aspx



回答3:

Ok, another answer along a different vein, but based on dev hedgehog's suggestion. Basically the idea is that an item's BringIntoView method is always called when it is actually in view. How this is determined is a bit mysterious, and what happens if say two items are scrolled into view is unknown. However some sample code that should capture all calls of BringIntoView:

   string guid = System.Guid.NewGuid().ToString();

   RoutedEvent scrollIntoViewEvent = EventManager.RegisterRoutedEvent(
                guid, 
                RoutingStrategy.Direct, 
                typeof(RequestBringIntoViewEventHandler), 
                typeof(ScrollViewer));

   EventManager.RegisterClassHandler(typeof(ScrollViewer), scrollIntoViewEvent, new RequestBringIntoViewEventHandler(this.RequestBringIntoView_Handler), true);

And an example handler:

   private void RequestBringIntoView_Handler(object sender, RequestBringIntoViewEventArgs e)
   {
       Object o = sender;
   }

A bit of tweeking here might just get this to capture all of the BringIntoView events, which should provide a solution for the original question as this handler does have the item passed into the sender.