Toutes les réponses ici utilisent simplement un TextBox
ou essaient d'implémenter la sélection de texte manuellement, ce qui conduit à de mauvaises performances ou à un comportement non natif (caret clignotant TextBox
, pas de prise en charge du clavier dans les implémentations manuelles, etc.)
Après des heures à fouiller et à lire le code source WPF , j'ai découvert un moyen d'activer la sélection de texte WPF native pour les TextBlock
contrôles (ou vraiment tout autre contrôle). La plupart des fonctionnalités de sélection de texte sont implémentées dans la System.Windows.Documents.TextEditor
classe système.
Pour activer la sélection de texte pour votre contrôle, vous devez faire deux choses:
Appelez TextEditor.RegisterCommandHandlers()
une fois pour enregistrer les gestionnaires d'événements de classe
Créer une instance de TextEditor
chaque instance de votre classe et passer l'instance sous - jacente de votre System.Windows.Documents.ITextContainer
à elle
Il est également nécessaire que la Focusable
propriété de votre contrôle soit définie sur True
.
Ça y est ...! Cela semble facile, mais malheureusement, la TextEditor
classe est marquée comme interne. J'ai donc dû écrire un wrapper de réflexion autour de lui:
class TextEditorWrapper
{
private static readonly Type TextEditorType = Type.GetType("System.Windows.Documents.TextEditor, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
private static readonly PropertyInfo IsReadOnlyProp = TextEditorType.GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
private static readonly PropertyInfo TextViewProp = TextEditorType.GetProperty("TextView", BindingFlags.Instance | BindingFlags.NonPublic);
private static readonly MethodInfo RegisterMethod = TextEditorType.GetMethod("RegisterCommandHandlers",
BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Type), typeof(bool), typeof(bool), typeof(bool) }, null);
private static readonly Type TextContainerType = Type.GetType("System.Windows.Documents.ITextContainer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
private static readonly PropertyInfo TextContainerTextViewProp = TextContainerType.GetProperty("TextView");
private static readonly PropertyInfo TextContainerProp = typeof(TextBlock).GetProperty("TextContainer", BindingFlags.Instance | BindingFlags.NonPublic);
public static void RegisterCommandHandlers(Type controlType, bool acceptsRichContent, bool readOnly, bool registerEventListeners)
{
RegisterMethod.Invoke(null, new object[] { controlType, acceptsRichContent, readOnly, registerEventListeners });
}
public static TextEditorWrapper CreateFor(TextBlock tb)
{
var textContainer = TextContainerProp.GetValue(tb);
var editor = new TextEditorWrapper(textContainer, tb, false);
IsReadOnlyProp.SetValue(editor._editor, true);
TextViewProp.SetValue(editor._editor, TextContainerTextViewProp.GetValue(textContainer));
return editor;
}
private readonly object _editor;
public TextEditorWrapper(object textContainer, FrameworkElement uiScope, bool isUndoEnabled)
{
_editor = Activator.CreateInstance(TextEditorType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance,
null, new[] { textContainer, uiScope, isUndoEnabled }, null);
}
}
J'ai également créé un SelectableTextBlock
dérivé de TextBlock
qui prend les étapes indiquées ci-dessus:
public class SelectableTextBlock : TextBlock
{
static SelectableTextBlock()
{
FocusableProperty.OverrideMetadata(typeof(SelectableTextBlock), new FrameworkPropertyMetadata(true));
TextEditorWrapper.RegisterCommandHandlers(typeof(SelectableTextBlock), true, true, true);
// remove the focus rectangle around the control
FocusVisualStyleProperty.OverrideMetadata(typeof(SelectableTextBlock), new FrameworkPropertyMetadata((object)null));
}
private readonly TextEditorWrapper _editor;
public SelectableTextBlock()
{
_editor = TextEditorWrapper.CreateFor(this);
}
}
Une autre option serait de créer une propriété jointe pour TextBlock
permettre la sélection de texte à la demande. Dans ce cas, pour désactiver à nouveau la sélection, il faut détacher un TextEditor
en utilisant l'équivalent de réflexion de ce code:
_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;