如何通过在 FlowDocument 中单击鼠标来获取 TextPointer

发布于 2024-09-04 19:10:38 字数 199 浏览 6 评论 0原文

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

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

然而,当点击发生在长跑即将结束时,这需要> 10秒。

有没有更有效的方法呢?

I would like to get the word that a user has clicked on in a FlowDocument.

I am currently adding an event handler to every Run in the document and iterating through the TextPointers in the Run that was clicked, calling GetCharacterRect() on each one and checking if the rectangle contains the point.

However, when the click occurs near the end of a long Run this takes > 10 seconds.

Is there any more efficient method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

青朷 2024-09-11 19:10:38

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

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获取要传递到扩展方法中的屏幕点。

I'd say the easiest way is to use the Automation interfaces:

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

The ITextProvider usage requires a reference to the UIAutomationProvider assembly. This assembly is not commonly referenced, so you may need to add it. UIAutomationTypes will also be needed to use some of its methods.

Note that there are many options for creating your automation peer depending on how you are presenting the 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);

Update

I tried this and it works well, though converting from an ITextRangeProvider to a TextPointer proved more difficult than I expected.

I packaged the algorithm in an extension method ScreenPointToTextPointer for easy use. Here is an example of how my extension method can be used to bold all text before the mouse pointer and un-bold all text after it:

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

Here is the code for the extension method:

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

}

Also note the use of PointToScreen in the example MouseMove method to get a screen point to pass into the extension method.

初见你 2024-09-11 19:10:38

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

If the FlowDocument is that of a RichTextBox, you could use the GetPositionFromPoint() method to get the TextPointer.

昔日梦未散 2024-09-11 19:10:38

鼠标单击事件会冒泡到顶部,您只需在文档中挂钩 PreviewMouseLeftButtonUp 并观察事件的发送者/原始来源,您将获得向您发送事件的运行。

然后您可以使用 RangeFromPoint 并使用

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

Mouse Click events are bubbled to the top, instead you can simply hook PreviewMouseLeftButtonUp in your document and watch for the sender/original source of the event, you will get the Run that sent you the event.

Then you can RangeFromPoint and you can use,

PointToScreen that will convert your local mouse point to global point.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文