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.
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
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
Bonnes questions. J'ai ajouté un code de travail à ma réponse qui montre exactement comment faire toutes ces choses. –