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

Posted

技术标签:

【中文标题】ScrollViewer - 指示子元素滚动到视图中【英文标题】:ScrollViewer - Indication of child element scrolled into view 【发布时间】:2013-10-24 05:38:06 【问题描述】:

当孩子滚动到视图中时是否会引发事件并指示实现了哪个孩子?

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

编辑:

我已尝试连接到 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>
      

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

【问题讨论】:

我必须这样做一次。您在“逻辑”孩子变得可见之后,而滚动主要是在像素的“物理”世界中。我的解决方案是拦截 Measure 和 Arrange 方法并调用一个 Action。不是完全干净,但在那次经历之后,我认为没有办法在键盘上不留下某种泥土。 【参考方案1】:

我认为您应该查看this article,它提供了一种判断控件是否对查看者可见的方法。

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

编辑:有效!方法如下:

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);

还有我的简单代码调用:

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
            
        
    

编辑:我从 dev Hedgehog 发布的链接中查看了 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);

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

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

【讨论】:

问题是,您将如何获得示例项目...。您必须跟踪可视化树中的所有项目并每次迭代它们.. 我每次都会检查它们。开销很大的不是繁重的代码。尽管您似乎已经明白了,但我可以使用该示例进行更新。浏览每个可能滚动到视图中的项目并在每次这种“可见性”发生变化时检查它会不会是一个问题? 我完全不喜欢这个设计,一定有更好的方法,感谢您的努力+1 是的,它肯定有它的问题,尤其是对我来说,我安排了我的示例滚动查看器,以便实际上可以看到两个项目。根据这些矩形的计算方式,即使只有下一项的顶部像素可见,您也可以获得“真”。在这种情况下,如果不对辅助方法进行更广泛的修改,您将无法分辨出哪个项目位于顶部,这有点笨拙。无论如何,祝你好运找到适合你的东西。也许这里还有一些东西可以让你朝着正确的方向前进。找到了请贴出来! 我理解对设计的担忧,但在这种情况下不会对性能或内存产生任何影响,所以对我来说这似乎是一个可以接受的解决方案。干得好。顺便检查一下 IsVisibleChanged 事件,只要有东西可见,就会触发该事件。如果 OP 可以只听这个,即使有 10 个项目可见,他也会收到 10 倍的通知。它是一个路由事件,可能会被父母控制。【参考方案2】:

新编辑:我再次阅读了你的问题,我意识到我一开始并不理解你。

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

我仍然有一些不清楚的地方:

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

ouflak 发布的答案根本不是糟糕的设计。其实就是普通的WPF。

如果您仍然不同意我们的建议,请查看 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://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.requestbringintoview.aspx

【讨论】:

这仅在 Item 被明确 RequestedIntoView 时才有效。如果项目通过其他方式进入视图,例如正常滚动,则不会触发该事件。 项目总是被请求查看 我尝试了你的建议,但我不确定我做对了。我将编辑问题,因为根本不可能在这些 cmets 中发布代码。 我在工作,我肯定会在几个小时内看看这个。 :) 所以现在我终于下班回家了,我看看这个。我想我误解了OP。当您在 ScrollViewer 中的第一个可见项目和最后一个可见项目之间“移动”自己时,可以使用 RequestBringIntoView,例如将焦点放在任何项目上,您会收到通知该项目在视图中。但是,OP 希望通过 InternalChildren 集合运行并确定孩子是否对用户可见。在这种情况下,我也同意您的解决方案 ouflak。顺便说一句,这里是 ScrollViewer 的源代码,也许你会在那里找到你们都在寻找的东西。我希望 OP 准确地告诉我们他想要什么【参考方案3】:

好的,另一个不同的答案,但基于dev hedgehog's 的建议。基本上这个想法是当一个项目实际在视图中时,它总是被调用的 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);

还有一个示例处理程序:

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

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

【讨论】:

这是个好主意,通过给它相同类型的事件参数,它也会知道如何引发这个事件(希望如此):), 我希望你能得到这个为你工作。我刚刚做了一个轻微的编辑,调用了一个不同的 RegisterClasshandler 覆盖,它将捕获设置了“Handled”标志的事件。 我认为在这种情况下这并不重要,因为除了我们之外没有人会处理这个事件。谢谢,稍后我会试试这个并告诉你。 这可以被删除。我现在明白OP在问什么。这是 ScrollViewer 源代码的链接。也许在那里你会发现一些有用的东西。 dotnetframework.org/default.aspx/Dotnetfx_Win7_3@5@1/… @ouflak 我有一个后续问题,你在哪里注册新的事件和处理程序? ,只是我看不到您如何将 RequestBringIntoViewEvent 与此新事件相关联。

以上是关于ScrollViewer - 指示子元素滚动到视图中的主要内容,如果未能解决你的问题,请参考以下文章

WPF,ScrollViewer的属性VerticalScrollBarVisibilityHorizontalScrollBarVisibility值的区别

UIWebView 的 UIScrollView 子视图可以滚动但会弹回框架而不出现垂直滚动指示器

如何在 UIScrollView 的滚动指示器下方放置非滚动 UIView?

旋转时 UIScrollView 的指示器消失

如何过滤列表视图中的内部子元素以及使用 Jquery 在移动应用程序中将字母滚动条添加到列表视图的任何简单方法

UIScrollView - 将视图移到前面,但在滚动指示器下方