4

我想获取用户在 FlowDocument 中单击的单词。

我目前正在为文档中的每个 Run 添加一个事件处理程序,并遍历单击的 Run 中的 TextPointers,对每个 Run 调用 GetCharacterRect() 并检查矩形是否包含该点。

但是,当点击发生在长时间运行接近尾声时,这需要 > 10 秒。

有没有更有效的方法?

4

3 回答 3

8

我想说最简单的方法是使用自动化接口:

using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;

FlowDocument flowDocument = ...;
Point point = ...;

var peer = new DocumentAutomationPeer(flowDocument);
var textProvider = (ITextProvider)peer.GetPattern(PatternInterface.Text);
var rangeProvider = textProvider.RangeFromPoint(point);

ITextProvider 的使用需要对 UIAutomationProvider 程序集的引用。此程序集不常被引用,因此您可能需要添加它。UIAutomationTypes 也需要使用它的一些方法。

请注意,根据您呈现 FlowDocument 的方式,有许多用于创建自动化对等点的选项:

var peer = new DocumentAutomationPeer(flowDocument);
var peer = new DocumentAutomationPeer(textBlock);
var peer = new DocumentAutomationPeer(flowDocumentScrollViewer);
var peer = new TextBoxAutomationPeer(textBox);
var peer = new RichTextBoxAutomationPeer(richTextBox);

更新

我尝试了这个并且效果很好,尽管从 ITextRangeProvider 转换为 TextPointer 证明比我预期的要困难。

ScreenPointToTextPointer为了方便使用,我将算法打包在一个扩展方法中。这是一个示例,说明如何使用我的扩展方法将鼠标指针之前的所有文本加粗并取消其后的所有文本加粗:

private void Window_MouseMove(object sender, MouseEventArgs e)
{
  var document = this.Viewer.Document;
  var screenPoint = PointToScreen(e.GetPosition(this));

  TextPointer pointer = document.ScreenPointToTextPointer(screenPoint);

  new TextRange(document.ContentStart, pointer).ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
  new TextRange(pointer, document.ContentEnd).ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Normal);
}

这是扩展方法的代码:

using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Text;

public static class DocumentExtensions
{
  // Point is specified relative to the given visual
  public static TextPointer ScreenPointToTextPointer(this FlowDocument document, Point screenPoint)
  {
    // Get text before point using automation
    var peer = new DocumentAutomationPeer(document);
    var textProvider = (ITextProvider)peer.GetPattern(PatternInterface.Text);
    var rangeProvider = textProvider.RangeFromPoint(screenPoint);
    rangeProvider.MoveEndpointByUnit(TextPatternRangeEndpoint.Start, TextUnit.Document, 1);
    int charsBeforePoint = rangeProvider.GetText(int.MaxValue).Length;

    // Find the pointer that corresponds to the TextPointer
    var pointer = document.ContentStart.GetPositionAtOffset(charsBeforePoint);

    // Adjust for difference between "text offset" and actual number of characters before pointer
    for(int i=0; i<10; i++)  // Limit to 10 adjustments
    {
      int error = charsBeforePoint - new TextRange(document.ContentStart, pointer).Text.Length;
      if(error==0) break;
      pointer = pointer.GetPositionAtOffset(error);
    }
    return pointer;
  }

}

还要注意在示例 MouseMove 方法中使用 PointToScreen 来获取要传递给扩展方法的屏幕点。

于 2010-06-06T05:22:49.887 回答
1

如果 FlowDocument 是 RichTextBox 的,您可以使用GetPositionFromPoint()方法来获取 TextPointer。

于 2011-07-01T10:57:34.873 回答
0

鼠标单击事件会冒泡到顶部,相反,您可以简单地将 PreviewMouseLeftButtonUp 挂接到文档中并注意事件的发送者/原始源,您将获得向您发送事件的运行。

然后你可以 RangeFromPoint 并且你可以使用,

PointToScreen 将您的本地鼠标点转换为全局点。

于 2010-06-06T05:49:37.170 回答