如何允许TextBlock的文本是可选的?

我试图让它通过显示文本使用只读文本框样式看起来像一个文本块,但这不会在我的情况下工作,因为一个文本框没有内联。换句话说,如何使它具有可选性?


当前回答

所有的答案在这里只是使用一个文本框或试图实现文本选择手动,这导致性能差或非本机行为(闪烁插入文本框,手动实现不支持键盘等)。

经过几个小时的挖掘和阅读WPF源代码,我反而发现了一种启用本地WPF文本选择TextBlock控件(或真正的任何其他控件)的方法。大多数关于文本选择的功能是在system . windows . documents . texteditor系统类中实现的。

要启用控件的文本选择,您需要做两件事:

调用一次texteitor . 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;

其他回答

为torvin的代码添加了Selection & SelectionChanged事件

public class SelectableTextBlock : TextBlock
{

    static readonly Type TextEditorType
    = Type.GetType("System.Windows.Documents.TextEditor, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");

    static readonly PropertyInfo IsReadOnlyProp
        = TextEditorType.GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);

    static readonly PropertyInfo TextViewProp
        = TextEditorType.GetProperty("TextView", BindingFlags.Instance | BindingFlags.NonPublic);

    static readonly MethodInfo RegisterMethod
        = TextEditorType.GetMethod("RegisterCommandHandlers",
        BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Type), typeof(bool), typeof(bool), typeof(bool) }, null);

    static readonly Type TextContainerType
        = Type.GetType("System.Windows.Documents.ITextContainer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
    static readonly PropertyInfo TextContainerTextViewProp
        = TextContainerType.GetProperty("TextView");

    static readonly PropertyInfo TextContainerTextSelectionProp
        = TextContainerType.GetProperty("TextSelection");

    static readonly PropertyInfo TextContainerProp = typeof(TextBlock).GetProperty("TextContainer", BindingFlags.Instance | BindingFlags.NonPublic);

    static void RegisterCommandHandlers(Type controlType, bool acceptsRichContent, bool readOnly, bool registerEventListeners)
    {
        RegisterMethod.Invoke(null, new object[] { controlType, acceptsRichContent, readOnly, registerEventListeners });
    }

    static SelectableTextBlock()
    {
        FocusableProperty.OverrideMetadata(typeof(SelectableTextBlock), new FrameworkPropertyMetadata(true));
        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;
    object? textContainer;
    object? editor;
    public TextSelection TextSelection { get; private set; }

    public SelectableTextBlock()
    {
        textContainer = TextContainerProp.GetValue(this);

        editor = Activator.CreateInstance(TextEditorType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance,
            null, new[] { textContainer, this, false }, null);


        IsReadOnlyProp.SetValue(editor, true);
        TextViewProp.SetValue(editor, TextContainerTextViewProp.GetValue(textContainer));

        TextSelection = (TextSelection)TextContainerTextSelectionProp.GetValue(textContainer);
        TextSelection.Changed += (s, e) => OnSelectionChanged?.Invoke(this, e);
    }

    public event EventHandler OnSelectionChanged;
}

我不确定你是否可以使一个TextBlock可选,但另一个选择是使用RichTextBox -它就像你建议的TextBox,但支持你想要的格式。

我已经实现了SelectableTextBlock在我的开源控件库。你可以这样使用它:

<jc:SelectableTextBlock Text="Some text" />

有一个替代的解决方案,可能适用于这篇博客文章中提到的RichTextBox -它使用一个触发器来交换控件模板,当使用悬停在控件上时-应该有助于性能

Really nice and easy solution, exactly what I wanted !

我带来了一些小改动

public class TextBlockMoo : TextBlock 
{
    public String SelectedText = "";

    public delegate void TextSelectedHandler(string SelectedText);
    public event TextSelectedHandler OnTextSelected;
    protected void RaiseEvent()
    {
        if (OnTextSelected != null){OnTextSelected(SelectedText);}
    }

    TextPointer StartSelectPosition;
    TextPointer EndSelectPosition;
    Brush _saveForeGroundBrush;
    Brush _saveBackGroundBrush;

    TextRange _ntr = null;

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
        base.OnMouseDown(e);

        if (_ntr!=null) {
            _ntr.ApplyPropertyValue(TextElement.ForegroundProperty, _saveForeGroundBrush);
            _ntr.ApplyPropertyValue(TextElement.BackgroundProperty, _saveBackGroundBrush);
        }

        Point mouseDownPoint = e.GetPosition(this);
        StartSelectPosition = this.GetPositionFromPoint(mouseDownPoint, true);            
    }

    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
        base.OnMouseUp(e);
        Point mouseUpPoint = e.GetPosition(this);
        EndSelectPosition = this.GetPositionFromPoint(mouseUpPoint, true);

        _ntr = new TextRange(StartSelectPosition, EndSelectPosition);

        // keep saved
        _saveForeGroundBrush = (Brush)_ntr.GetPropertyValue(TextElement.ForegroundProperty);
        _saveBackGroundBrush = (Brush)_ntr.GetPropertyValue(TextElement.BackgroundProperty);
        // change style
        _ntr.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Yellow));
        _ntr.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.DarkBlue));

        SelectedText = _ntr.Text;
    }
}