2010-06-05 65 views
3

我想要获取用户在FlowDocument中单击的单词。如何从FlowDocument中的鼠标单击中获取TextPointer

我正在为文档中的每个运行添加一个事件处理程序,并通过运行中被单击的TextPointers迭代,每个运行时调用GetCharacterRect()并检查该矩形是否包含该点。

但是,当点击发生在长运行结束附近时,这需要大于10秒。

有没有更有效的方法?

回答

6

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

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

+0

这似乎是一个有趣的解决方案。 RangeFromPoint方法似乎需要屏幕(绝对)坐标中的点,而不是鼠标事件提供的相对坐标。我将如何转换坐标? – yclevine 2010-06-06 06:17:31

+0

此解决方案的两个问题: 1. range.GetText()返回零长度字符串。 2.如果我确实得到了一个字符串,我怎么知道字符串中的哪个单词对应于鼠标单击的位置? – yclevine 2010-06-06 11:47:03

+0

好问题。我已经在我的答案中添加了工作代码,以显示如何完成所有这些工作。 – 2010-06-08 21:26:34

0

鼠标点击事件冒泡到顶部,您可以简单地在文档中挂接PreviewMouseLeftButtonUp,并观察事件的发件人/原始来源,您将获得向您发送事件的Run。

然后你就可以RangeFromPoint,你可以使用,

PointToScreen将将使用本地鼠标点转换为全局点。

+0

我已经可以轻松获得Run。问题是让TextPointer最接近点击。 – yclevine 2010-06-06 06:13:58

+0

运行是从TextElement派生的,它具有ContentStart和ContentEnd,它们可以是最接近的TextPointer,您是在寻找RunPointer中的TextPointer吗? – 2010-06-06 06:52:09

+0

是的。我需要确切的TextPointer,以便我可以找出被单击的单词并提供有关该单词的其他信息(字典查找)。 – yclevine 2010-06-06 10:56:39

相关问题