wpf ScrollViewer - 指示子元素滚动到视图中

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19397780/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 09:52:15  来源:igfitidea点击:

ScrollViewer - Indication of child element scrolled into view

wpfscrollviewer

提问by eran otzap

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.

当然有 ScrollChanged 事件,但它没有向我提供任何关于滚动到视图中的元素的指示。

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:

我尝试连接到 ScrollViewer 的 RequestBringIntoView 事件,但从未达到。或者,我也在包含以下项目的 StackPanel 上尝试了相同的方法:

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

他们永远无法到达。据我了解,ScrollViewer 在其封装的子元素上调用BringIntoView 并引发RequestBringIntoView 事件,我希望该事件向上传播可视化树。我猜 ScrollViewer 在内部处理该事件。所以我最终遇到了同样的问题,即当它的孩子出现时如何得到通知。我可以将它们中的每一个连接起来,或者 ItemsControl 会为我做这件事..?

回答by ouflak

I think you should look at this articlewhich 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....

如果您要在 ScrollChanged 处理程序中调用该自定义方法,从而在每次滚动时都进行检查,我认为这可以解决问题。我会自己试试这个......

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:

编辑:我从开发刺猬发布的链接中查看了 ScrollViewer 的源代码,发现了这个有趣的私有函数:

// 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 whocalls this method, where, and why.

这显然表明,即使是 ScrollViewer 本身也有兴趣知道什么是可见的,并且正如我所预期的那样,基本上执行与该辅助方法相同类型的计算。下载此代码并查看调用此方法、在何处以及为何调用可能是值得的。

Edit:Looks like its called by OnKeyDown() and used to determine focus behavior, and that's it. Interesting....

编辑:看起来它由 OnKeyDown() 调用并用于确定焦点行为,仅此而已。有趣的....

回答by dev hedgehog

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.

抱歉,我以为您的意思是您希望在将鼠标移到 ScrollViewer 的视口内时收到通知,或者在第一个可见项目或最后一个可见项目之间设置焦点。这就是 RequestBringIntoView 派上用场的时候。

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?

“当一个孩子被滚动到视图中时,是否会引发一个事件,表明什么孩子被实现了?” - 你说的是普通面板还是 VirtualizingStackPanel?

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

ouflak 发布的答案根本不是糟糕的设计。它实际上是通常的 WPF。

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

如果您仍然不同意我们的建议,请查看 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

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/系统/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

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

回答by ouflak

Ok, another answer along a different vein, but based on dev hedgehog'ssuggestion. 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:

好的,另一个不同的答案,但基于开发刺猬的建议。基本上这个想法是当一个项目的BringIntoView 方法总是在它实际在视图中时被调用。这是如何确定的有点神秘,如果说两个项目滚动到视图中会发生什么是未知的。然而,一些示例代码应该捕获所有的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.

这里的一些调整可能只是为了捕获所有的BringIntoView 事件,这应该为原始问题提供解决方案,因为此处理程序确实将项目传递给了发件人。