这里的所有答案都只是使用TextBox 或尝试手动实现文本选择,这会导致性能不佳或非本地行为(TextBox 中的插入符号闪烁,手动实现中不支持键盘等)
经过数小时的挖掘和阅读WPF source code,我发现了一种为TextBlock 控件(或实际上任何其他控件)启用本机WPF 文本选择的方法。围绕文本选择的大部分功能都在System.Windows.Documents.TextEditor 系统类中实现。
要为您的控件启用文本选择,您需要做两件事:
拨打TextEditor.RegisterCommandHandlers()一次注册课程
事件处理程序
为您的类的每个实例创建一个TextEditor 的实例,并将您的System.Windows.Documents.ITextContainer 的底层实例传递给它
还需要将控件的Focusable 属性设置为True。
就是这样!听起来很简单,但不幸的是TextEditor 类被标记为内部。所以我不得不围绕它写一个反射包装器:
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);
}
}
我还创建了一个从TextBlock 派生的SelectableTextBlock,它采用上述步骤:
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);
}
}
另一种选择是为TextBlock 创建一个附加属性,以便按需启用文本选择。在这种情况下,要再次禁用选择,需要使用此代码的反射等效项来分离 TextEditor:
_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;