Alle Antworten hier verwenden lediglich eine TextBox
oder versuchen, die Textauswahl manuell zu implementieren, was zu einer schlechten Leistung oder einem nicht nativen Verhalten führt (blinkendes Caret in)TextBox
, keine Tastaturunterstützung bei manuellen Implementierungen usw.).
Nachdem ich mich stundenlang umgesehen und den WPF-Quellcode gelesen hatte , entdeckte ich stattdessen eine Möglichkeit, die native WPF-Textauswahl für TextBlock
Steuerelemente (oder wirklich alle anderen Steuerelemente) zu aktivieren . Die meisten Funktionen zur Textauswahl sind in der Systemklasse implementiert System.Windows.Documents.TextEditor
.
Um die Textauswahl für Ihr Steuerelement zu aktivieren, müssen Sie zwei Dinge tun:
Rufen Sie TextEditor.RegisterCommandHandlers()
einmal an, um Klassenereignishandler zu registrieren
Erstellen Sie eine Instanz von TextEditor
für jede Instanz Ihrer Klasse und übergeben Sie die zugrunde liegende Instanz Ihrer Klasse System.Windows.Documents.ITextContainer
an diese
Es gibt auch eine Anforderung, auf die die Focusable
Eigenschaft Ihres Steuerelements festgelegt ist True
.
Das ist es! Klingt einfach, aber leider TextEditor
ist die Klasse als intern markiert. Also musste ich einen Reflection Wrapper darum schreiben:
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);
}
}
Ich habe auch eine SelectableTextBlock
Ableitung erstellt TextBlock
, die die oben genannten Schritte ausführt:
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);
}
}
Eine andere Möglichkeit wäre, eine angehängte Eigenschaft zu erstellen TextBlock
, um die Textauswahl bei Bedarf zu ermöglichen. In diesem Fall muss zum Löschen der Auswahl a TextEditor
unter Verwendung des Reflexionsäquivalents dieses Codes getrennt werden:
_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;