2010-06-05 5 views
3

Je voudrais obtenir le mot sur lequel un utilisateur a cliqué dans un FlowDocument.Comment puis-je obtenir un TextPointer à partir d'un clic de souris dans un FlowDocument?

J'ajoute actuellement un gestionnaire d'événement à chaque exécution dans le document et j'itère les TextPointers de l'exécution sur lesquels j'ai cliqué, en appelant GetCharacterRect() sur chacun d'eux et en vérifiant si le rectangle contient le point. Cependant, lorsque le clic se produit vers la fin d'une longue course, cela prend> 10 secondes.

Existe-t-il une méthode plus efficace?

Répondre

6

Je dirais que la façon la plus simple est d'utiliser les interfaces d'automatisation:

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

L'utilisation de ITextProvider nécessite une référence à l'assemblée UIAutomationProvider. Cet assembly n'est pas couramment référencé, vous devrez donc peut-être l'ajouter. UIAutomationTypes sera également nécessaire pour utiliser certaines de ses méthodes.

Notez qu'il existe de nombreuses options pour la création de votre poste d'automatisation selon la façon dont vous présentez la 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); 

Mise à jour

J'ai essayé et il fonctionne bien, bien que la conversion d'un ITextRangeProvider à un TextPointer s'est avéré plus difficile que prévu.

J'ai emballé l'algorithme dans une méthode d'extension ScreenPointToTextPointer pour une utilisation facile. Voici un exemple de la façon dont ma méthode d'extension peut être utilisé pour tout le texte en gras avant le pointeur de la souris et tout le texte non gras après:

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

Voici le code de la méthode d'extension:

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

} 

Notez également l'utilisation de PointToScreen dans l'exemple de méthode MouseMove pour obtenir un point d'écran à transmettre à la méthode d'extension.

+0

Cela semble être une solution intéressante. La méthode RangeFromPoint semble exiger un point dans les coordonnées d'écran (absolues) par opposition aux coordonnées relatives fournies par l'événement de souris. Comment pourrais-je convertir les coordonnées? – yclevine

+0

Deux problèmes avec cette solution: 1. range.GetText() renvoie une chaîne de longueur nulle. 2. Si je recevais une chaîne, comment saurais-je quel mot de la chaîne correspond à l'emplacement du clic de la souris? – yclevine

+0

Bonnes questions. J'ai ajouté un code de travail à ma réponse qui montre exactement comment faire toutes ces choses. –

0

Les événements clic de souris sont propulsés vers le haut, vous pouvez simplement accrocher PreviewMouseLeftButtonUp dans votre document et regarder l'expéditeur/origine de l'événement, vous obtiendrez l'exécution qui vous a envoyé l'événement.

Ensuite, vous pouvez RangeFromPoint et vous pouvez utiliser,

PointToScreen qui convertiront votre point de souris local pour point global.

+0

Je peux déjà facilement obtenir la course. Le problème consiste à obtenir le TextPointer le plus proche du clic. – yclevine

+0

Exécuter est dérivé de TextElement et ContentStart et ContentEnd peuvent être les plus proches TextPointer, recherchez-vous TextPointer dans Run? –

+0

Oui. J'ai besoin du TextPointer exact, afin que je puisse comprendre le mot qui a été cliqué et fournir des informations supplémentaires (recherche de dictionnaire) sur ce mot. – yclevine

2

Si le FlowDocument est celui d'un RichTextBox, vous pouvez utiliser la méthode GetPositionFromPoint() pour obtenir le TextPointer.