如何检测 FlowDocumentScrollViewer 视图中可见的部分?

问题描述 投票:0回答:1

我在

FlowDocument
中有多个部分,如下所示。由于
FlowDocument
内部有多个块,并且
FlowDocument
位于
FlowDocumentScrollViewer
内部,因此可以滚动内容。在这里,我试图检测当前在视图中可见的块。例如,在下图中,
<Section Name="sec2"> ... </Section>
可见。

主窗口.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <FlowDocumentScrollViewer IsToolBarVisible="False" MinZoom="50" Zoom="100"   BorderThickness="1" BorderBrush="Gainsboro" Name="flowDoc" Loaded="FlowDoc_Loaded">
            <FlowDocument Name="flowdoc1" ColumnWidth="999999" PageHeight="840">
                <Section Name="sec1">
                    <Paragraph>
                        <Run>
                            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec maximus libero. Proin at suscipit tellus. Maecenas interdum lacinia turpis, nec dictum nisi blandit ac. Mauris quis mauris sodales, aliquet tortor nec, dignissim turpis. Sed nec purus vitae tortor posuere tempus id at justo. Integer maximus, eros sit amet sollicitudin cursus, urna lacus posuere justo, ut tempus est nisi nec elit. Duis luctus, justo sed feugiat dapibus, arcu odio hendrerit est, tempus tempus ante elit ut augue. Quisque aliquet blandit libero sed congue. Quisque imperdiet tempor enim, at pulvinar nulla pellentesque in. Maecenas sit amet massa ultrices, fringilla leo id, scelerisque turpis. Cras non pharetra metus. Cras blandit hendrerit nulla at pellentesque. Nulla tristique eget nibh sed ornare. Praesent augue purus, tincidunt a tempus ut, iaculis vel mi.
                        </Run>
                        <Run>
                            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent molestie volutpat ante, ac dapibus risus varius vel. In tempor nulla non tristique scelerisque. Nam sollicitudin est tellus, sit amet tempus quam faucibus vel. Proin accumsan sed est ut facilisis. Sed non elit semper, euismod augue non, vulputate lorem. Duis malesuada fringilla mauris, ut tempus enim volutpat in. Duis dignissim ullamcorper est in auctor. Nam at tempor metus, in blandit ligula. Mauris sed urna eleifend, volutpat nulla at, ornare libero. Pellentesque dictum ac nibh at fringilla. Pellentesque eu mi leo. Proin aliquet ante at nisi consequat pharetra.
                        </Run>
                        <Run>
                            Etiam accumsan arcu justo, quis dignissim elit volutpat id. Maecenas nec eros id massa pellentesque euismod nec et libero. Aliquam in porttitor lectus. Mauris nec magna sit amet mauris dignissim blandit eget eget velit. Vestibulum dapibus tempor erat, in dapibus eros vestibulum venenatis. Aenean massa mi, efficitur quis fringilla in, elementum vel metus. Sed pulvinar ex lacinia lobortis pharetra. In vel nisi aliquet, porttitor purus vitae, tempus orci. Morbi et ipsum rhoncus, aliquam tortor et, iaculis nibh.
                        </Run>
                        <Run>
                            Curabitur hendrerit nisl ut erat viverra pharetra. Fusce rhoncus vitae nisl ac venenatis. Etiam eget magna vitae dolor placerat vestibulum. Maecenas et tristique orci, et molestie risus. In consectetur odio mi, at sollicitudin mauris aliquam in. Aenean a est vehicula lectus semper tempus id sed diam. Suspendisse potenti. Donec accumsan tortor ac lobortis hendrerit. Phasellus vel pellentesque tortor. Curabitur consectetur luctus nibh, non interdum orci placerat in. Pellentesque tempus est tellus, sit amet tempor eros rhoncus vitae. Nunc lobortis turpis sed condimentum mattis. Aliquam erat volutpat.
                        </Run>
                        <Run>
                            Proin ut pellentesque felis. Curabitur faucibus sed lectus id ornare. Suspendisse tempor pellentesque quam sit amet gravida. Sed venenatis lorem sit amet metus luctus efficitur. Ut enim nisl, luctus quis mollis vitae, aliquam in est. Donec et lobortis sapien. Aliquam suscipit augue a nunc tincidunt, vel sodales velit dapibus. Nulla lacinia mi sit amet libero scelerisque, at hendrerit eros vehicula. Nam lectus metus, faucibus non quam eu, dignissim ultrices metus. Cras urna libero, molestie euismod tortor id, ultrices posuere ligula. Nulla porta maximus nulla. Nunc vitae finibus diam, eu consequat tortor.
                        </Run>
                    </Paragraph>
                </Section>
                <Section Name="sec2">
                    <Paragraph>
                        <Run>
                            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec maximus libero. Proin at suscipit tellus. Maecenas interdum lacinia turpis, nec dictum nisi blandit ac. Mauris quis mauris sodales, aliquet tortor nec, dignissim turpis. Sed nec purus vitae tortor posuere tempus id at justo. Integer maximus, eros sit amet sollicitudin cursus, urna lacus posuere justo, ut tempus est nisi nec elit. Duis luctus, justo sed feugiat dapibus, arcu odio hendrerit est, tempus tempus ante elit ut augue. Quisque aliquet blandit libero sed congue. Quisque imperdiet tempor enim, at pulvinar nulla pellentesque in. Maecenas sit amet massa ultrices, fringilla leo id, scelerisque turpis. Cras non pharetra metus. Cras blandit hendrerit nulla at pellentesque. Nulla tristique eget nibh sed ornare. Praesent augue purus, tincidunt a tempus ut, iaculis vel mi.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent molestie volutpat ante, ac dapibus risus varius vel. In tempor nulla non tristique scelerisque. Nam sollicitudin est tellus, sit amet tempus quam faucibus vel. Proin accumsan sed est ut facilisis. Sed non elit semper, euismod augue non, vulputate lorem. Duis malesuada fringilla mauris, ut tempus enim volutpat in. Duis dignissim ullamcorper est in auctor. Nam at tempor metus, in blandit ligula. Mauris sed urna eleifend, volutpat nulla at, ornare libero. Pellentesque dictum ac nibh at fringilla. Pellentesque eu mi leo. Proin aliquet ante at nisi consequat pharetra.

Etiam accumsan arcu justo, quis dignissim elit volutpat id. Maecenas nec eros id massa pellentesque euismod nec et libero. Aliquam in porttitor lectus. Mauris nec magna sit amet mauris dignissim blandit eget eget velit. Vestibulum dapibus tempor erat, in dapibus eros vestibulum venenatis. Aenean massa mi, efficitur quis fringilla in, elementum vel metus. Sed pulvinar ex lacinia lobortis pharetra. In vel nisi aliquet, porttitor purus vitae, tempus orci. Morbi et ipsum rhoncus, aliquam tortor et, iaculis nibh.

Curabitur hendrerit nisl ut erat viverra pharetra. Fusce rhoncus vitae nisl ac venenatis. Etiam eget magna vitae dolor placerat vestibulum. Maecenas et tristique orci, et molestie risus. In consectetur odio mi, at sollicitudin mauris aliquam in. Aenean a est vehicula lectus semper tempus id sed diam. Suspendisse potenti. Donec accumsan tortor ac lobortis hendrerit. Phasellus vel pellentesque tortor. Curabitur consectetur luctus nibh, non interdum orci placerat in. Pellentesque tempus est tellus, sit amet tempor eros rhoncus vitae. Nunc lobortis turpis sed condimentum mattis. Aliquam erat volutpat.

Proin ut pellentesque felis. Curabitur faucibus sed lectus id ornare. Suspendisse tempor pellentesque quam sit amet gravida. Sed venenatis lorem sit amet metus luctus efficitur. Ut enim nisl, luctus quis mollis vitae, aliquam in est. Donec et lobortis sapien. Aliquam suscipit augue a nunc tincidunt, vel sodales velit dapibus. Nulla lacinia mi sit amet libero scelerisque, at hendrerit eros vehicula. Nam lectus metus, faucibus non quam eu, dignissim ultrices metus. Cras urna libero, molestie euismod tortor id, ultrices posuere ligula. Nulla porta maximus nulla. Nunc vitae finibus diam, eu consequat tortor.
                        </Run>
                    </Paragraph>
                </Section>
                <Section Name="sec3">
                    <Paragraph>
                        <Run>
                            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec maximus libero. Proin at suscipit tellus. Maecenas interdum lacinia turpis, nec dictum nisi blandit ac. Mauris quis mauris sodales, aliquet tortor nec, dignissim turpis. Sed nec purus vitae tortor posuere tempus id at justo. Integer maximus, eros sit amet sollicitudin cursus, urna lacus posuere justo, ut tempus est nisi nec elit. Duis luctus, justo sed feugiat dapibus, arcu odio hendrerit est, tempus tempus ante elit ut augue. Quisque aliquet blandit libero sed congue. Quisque imperdiet tempor enim, at pulvinar nulla pellentesque in. Maecenas sit amet massa ultrices, fringilla leo id, scelerisque turpis. Cras non pharetra metus. Cras blandit hendrerit nulla at pellentesque. Nulla tristique eget nibh sed ornare. Praesent augue purus, tincidunt a tempus ut, iaculis vel mi.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent molestie volutpat ante, ac dapibus risus varius vel. In tempor nulla non tristique scelerisque. Nam sollicitudin est tellus, sit amet tempus quam faucibus vel. Proin accumsan sed est ut facilisis. Sed non elit semper, euismod augue non, vulputate lorem. Duis malesuada fringilla mauris, ut tempus enim volutpat in. Duis dignissim ullamcorper est in auctor. Nam at tempor metus, in blandit ligula. Mauris sed urna eleifend, volutpat nulla at, ornare libero. Pellentesque dictum ac nibh at fringilla. Pellentesque eu mi leo. Proin aliquet ante at nisi consequat pharetra.

Etiam accumsan arcu justo, quis dignissim elit volutpat id. Maecenas nec eros id massa pellentesque euismod nec et libero. Aliquam in porttitor lectus. Mauris nec magna sit amet mauris dignissim blandit eget eget velit. Vestibulum dapibus tempor erat, in dapibus eros vestibulum venenatis. Aenean massa mi, efficitur quis fringilla in, elementum vel metus. Sed pulvinar ex lacinia lobortis pharetra. In vel nisi aliquet, porttitor purus vitae, tempus orci. Morbi et ipsum rhoncus, aliquam tortor et, iaculis nibh.

Curabitur hendrerit nisl ut erat viverra pharetra. Fusce rhoncus vitae nisl ac venenatis. Etiam eget magna vitae dolor placerat vestibulum. Maecenas et tristique orci, et molestie risus. In consectetur odio mi, at sollicitudin mauris aliquam in. Aenean a est vehicula lectus semper tempus id sed diam. Suspendisse potenti. Donec accumsan tortor ac lobortis hendrerit. Phasellus vel pellentesque tortor. Curabitur consectetur luctus nibh, non interdum orci placerat in. Pellentesque tempus est tellus, sit amet tempor eros rhoncus vitae. Nunc lobortis turpis sed condimentum mattis. Aliquam erat volutpat.

Proin ut pellentesque felis. Curabitur faucibus sed lectus id ornare. Suspendisse tempor pellentesque quam sit amet gravida. Sed venenatis lorem sit amet metus luctus efficitur. Ut enim nisl, luctus quis mollis vitae, aliquam in est. Donec et lobortis sapien. Aliquam suscipit augue a nunc tincidunt, vel sodales velit dapibus. Nulla lacinia mi sit amet libero scelerisque, at hendrerit eros vehicula. Nam lectus metus, faucibus non quam eu, dignissim ultrices metus. Cras urna libero, molestie euismod tortor id, ultrices posuere ligula. Nulla porta maximus nulla. Nunc vitae finibus diam, eu consequat tortor.
                        </Run>
                    </Paragraph>
                </Section>
            </FlowDocument>
        </FlowDocumentScrollViewer>
    </Grid>
</Window>

ScrollChanged
FlowDocumentScrollViewer
上,我试图获取当前在视图中可见的部分。我尝试了以下方法,但找不到实现此目的的实际方法。

MainWindow.xaml.cs

using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void FlowDoc_Loaded(object sender, RoutedEventArgs e)
        {
            ScrollViewer sv = flowDoc.Template.FindName("PART_ContentHost", flowDoc) as ScrollViewer;
            sv.ScrollChanged += Sv_ScrollChanged;
        }

        private void Sv_ScrollChanged(object sender, ScrollChangedEventArgs e) 
        {
            var blocks1 = flowdoc1.Blocks.ToList();
            
            foreach(var block in blocks1) 
            {
                
                //block.
                var cstartPoint = block.ContentStart;
                var cendPoint = block.ContentEnd;
                
                var estartPoint = block.ElementStart;
                var eendPoint = block.ElementEnd;
                
                bool isFoucsed = block.IsFocused;
                
                //sysWindow.Rect test1 = new sysWindow.Rect(block1);
            }
        }
    }
}

有什么解决办法吗?

c# wpf xaml flowdocument flowdocumentscrollviewer
1个回答
0
投票

您只需检查从

TextPointer.GetCharacterRect
获得的边界是否在
ScrollViewer
的视口范围内。

以下示例递归地遍历文档的

TextElement
树并引发
TextElementScrolledIntoViewChanged
事件,该事件会在任何
TextElement
进入或离开视口时发出通知。

使用示例

private void FlowDoc_Loaded(object sender, RoutedEventArgs e)
{
  var textElementObserver = new TextElementObserver()
  {
    IsNotifyOnScrollIntoViewEnabled = true,
    IsObserveTextElementsRecursiveEnabled = true
  };

  textElementObserver.TextElementScrolledIntoViewChanged += OnTextElementScrolledIntoViewChanged;

  var documentHost = (Control)sender;
  textElementObserver.ObserveFlowDocumentHost(documentHost);
}

private void OnTextElementScrolledIntoViewChanged(object? sender, TextElementScrolledIntoViewChangedEventArgs e)
{
  if (e.TextElement is Section section 
    && e.ScrollState is ScrollState.EnterView or ScrollState.LeaveView)
  {
    string visibleSectionName = section.Name;
  }
}

实施

TextElementObserver.cs

public class TextElementObserver
{
  internal class TextElementInfo
  {
    public TextElementInfo(TextElement textElement, bool isLeaving)
    {
      this.TextElement = textElement;
      this.IsLeaving = isLeaving;
    }

    public TextElement TextElement { get; }
    public bool IsLeaving { get; set; }
  }

  public event EventHandler<TextElementScrolledIntoViewChangedEventArgs> TextElementScrolledIntoViewChanged;
  public bool IsNotifyOnScrollIntoViewEnabled { get; set; }
  public bool IsObserveTextElementsRecursiveEnabled { get; set; }
  private Dictionary<Control, FlowDocument> DocumentHostToDocumentMap { get; }
  private Dictionary<TextElement, TextElementInfo> VisibleTextElementsMap { get; }

  public TextElementObserver()
  {
    this.DocumentHostToDocumentMap = new Dictionary<Control, FlowDocument>();
    this.VisibleTextElementsMap = new Dictionary<TextElement, TextElementInfo>();
  }

  public void ObserveFlowDocumentHost(Control flowDocumentHost)
  {
    FlowDocument document = flowDocumentHost switch
    {
      DocumentViewerBase documentViewerBase => documentViewerBase.Document is FlowDocument flowDocument ? flowDocument : throw new NotSupportedException(),
      FlowDocumentReader flowDocumentReader => flowDocumentReader.Document,
      FlowDocumentScrollViewer flowDocumentScrollViewer => flowDocumentScrollViewer.Document,
      RichTextBox richTextBox => richTextBox.Document,
      _ => throw new NotSupportedException()
    };

    if (!this.DocumentHostToDocumentMap.ContainsKey(flowDocumentHost))
    {
      flowDocumentHost.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(OnDocumentScrolled));
    }

    // Add or update
    this.DocumentHostToDocumentMap[flowDocumentHost] = document;
  }

  private void OnDocumentScrolled(object sender, ScrollChangedEventArgs e)
  {
    if (!this.IsNotifyOnScrollIntoViewEnabled)
    {
      return;
    }

    var documentHost = (Control)sender;
    if (!this.DocumentHostToDocumentMap.TryGetValue(documentHost, out FlowDocument flowDocument))
    {
      return;
    }

    bool isSrollDirectionDown = e.VerticalChange > 0;
    var scrollViewer = (ScrollViewer)e.OriginalSource;
    Rect viewPortBounds = new Rect(scrollViewer.TranslatePoint(new Point(0, 0), documentHost), new Size(scrollViewer.ViewportWidth, scrollViewer.ViewportHeight));
    LocateTextElements(viewPortBounds, flowDocument.Blocks, isSrollDirectionDown);
  }

  // Recursively traverses the documents TextElement tree
  private void LocateTextElements(Rect viewPortBounds, IEnumerable<TextElement> textElements, bool isSrollDirectionDown)
  {
    foreach (TextElement textElement in textElements)
    {
      Rect textElementStartBounds = textElement.ContentStart.GetCharacterRect(LogicalDirection.Forward);
      Rect textElementEndBounds = textElement.ContentEnd.GetCharacterRect(LogicalDirection.Forward);
      bool isStartOfBlockVisible = viewPortBounds.Contains(textElementStartBounds);
      bool isEndOfBlockVisible = viewPortBounds.Contains(textElementEndBounds);
      if (isSrollDirectionDown)
      {
        HandleScrollDown(textElement, isStartOfBlockVisible, isEndOfBlockVisible);
      }
      else
      {
        HandleScrollUp(textElement, isStartOfBlockVisible, isEndOfBlockVisible);
      }

      if (!this.IsObserveTextElementsRecursiveEnabled)
      {
        continue;
      }

      IEnumerable<TextElement> childTextElements = textElement switch
      {
        Section section => section.Blocks,
        Paragraph paragraph => paragraph.Inlines,
        Table table => table.RowGroups,
        List list => list.ListItems,
        ListItem listItem => listItem.Blocks,
        TableCell tableCell => tableCell.Blocks,
        TableRow tableRow => tableRow.Cells,
        TableRowGroup tableRowGroup => tableRowGroup.Rows,
        AnchoredBlock anchoredBlock => anchoredBlock.Blocks,
        Span span => span.Inlines,
        _ => Enumerable.Empty<TextElement>()
      };

      if (childTextElements.Any())
      {
        LocateTextElements(viewPortBounds, childTextElements, isSrollDirectionDown);
      }
    }
  }

  private void HandleScrollDown(TextElement textElement, bool isStartOfBlockVisible, bool isEndOfBlockVisible)
  {
    TextElementInfo textElementInfo;
    if (isStartOfBlockVisible)
    {
      if (this.VisibleTextElementsMap.TryGetValue(textElement, out textElementInfo))
      {
        textElementInfo.IsLeaving = false;
      }
      else
      {
        this.VisibleTextElementsMap.Add(textElement, new TextElementInfo(textElement, false));
        OnTextElementScrolledIntoViewChanged(textElement, ScrollState.EnterView);
        return;
      }
    }

    if (this.VisibleTextElementsMap.TryGetValue(textElement, out textElementInfo))
    {
      if (isEndOfBlockVisible)
      {
        textElementInfo.IsLeaving = true;
      }
      else if (textElementInfo.IsLeaving)
      {
        _ = this.VisibleTextElementsMap.Remove(textElement);
        OnTextElementScrolledIntoViewChanged(textElement, ScrollState.LeaveView);
      }
    }
  }

  private void HandleScrollUp(TextElement textElement, bool isStartOfBlockVisible, bool isEndOfBlockVisible)
  {
    TextElementInfo textElementInfo;
    if (isEndOfBlockVisible)
    {
      if (this.VisibleTextElementsMap.TryGetValue(textElement, out textElementInfo))
      {
        textElementInfo.IsLeaving = false;
      }
      else
      {
        this.VisibleTextElementsMap.Add(textElement, new TextElementInfo(textElement, false));
        OnTextElementScrolledIntoViewChanged(textElement, ScrollState.EnterView);
        return;
      }
    }

    if (this.VisibleTextElementsMap.TryGetValue(textElement, out textElementInfo))
    {
      if (isStartOfBlockVisible)
      {
        textElementInfo.IsLeaving = true;
      }
      else if (textElementInfo.IsLeaving)
      {
        _ = this.VisibleTextElementsMap.Remove(textElement);
        OnTextElementScrolledIntoViewChanged(textElement, ScrollState.LeaveView);
      }
    }
  }

  protected virtual void OnTextElementScrolledIntoViewChanged(TextElement textElement, ScrollState scrollState)
    => this.TextElementScrolledIntoViewChanged?.Invoke(this, new TextElementScrolledIntoViewChangedEventArgs(textElement, scrollState));
}

TextElementScrolledIntoViewChangedEventArgs.cs

public class TextElementScrolledIntoViewChangedEventArgs : EventArgs
{
  public TextElementScrolledIntoViewChangedEventArgs(TextElement textElement, ScrollState scrollState)
  {
    this.TextElement = textElement;
    this.ScrollState = scrollState;
  }

  public TextElement TextElement { get; }
  public ScrollState ScrollState { get; }
}

ScrollState.cs

public enum ScrollState
{
  Undefined = 0,
  EnterView,
  LeaveView
}
© www.soinside.com 2019 - 2024. All rights reserved.