2010-06-05 66 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

相關問題