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

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


当前回答

为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;
}

其他回答

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

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

TextBlock没有模板。因此,为了实现这一点,我们需要使用一个文本框,其风格被改变为一个文本块的行为。

<Style x:Key="TextBlockUsingTextBoxStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Padding" Value="1"/>
    <Setter Property="AllowDrop" Value="true"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
    <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <TextBox BorderThickness="{TemplateBinding BorderThickness}" IsReadOnly="True" Text="{TemplateBinding Text}" Background="{x:Null}" BorderBrush="{x:Null}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

只是在FlowDocumentScrollViewer中使用FlowDocument,将你的内联传递给元素。 你可以控制元素的样式,在我的例子中,我添加了一个小边框。

<FlowDocumentScrollViewer Grid.Row="2" Margin="5,3" BorderThickness="1" 
                          BorderBrush="{DynamicResource Element.Border}" 
                          VerticalScrollBarVisibility="Auto">
    <FlowDocument>
        <Paragraph>
            <Bold>Some bold text in the paragraph.</Bold>
            Some text that is not bold.
        </Paragraph>

        <List>
            <ListItem>
                <Paragraph>ListItem 1</Paragraph>
            </ListItem>
            <ListItem>
                <Paragraph>ListItem 2</Paragraph>
            </ListItem>
            <ListItem>
                <Paragraph>ListItem 3</Paragraph>
            </ListItem>
        </List>
    </FlowDocument>
</FlowDocumentScrollViewer>

我同意大多数答案在这里不创建一个可选的文本块。@Billy Willoughby的工作得很好,但是它没有一个明显的选择线索。我想扩展他的扩展,可以突出显示文本,因为它被选中。它还包含双击和三次点击选择。如果需要,您可以添加带有“Copy”的上下文菜单。 它使用Background属性来“突出显示”所选内容,因此它会覆盖Run。背景

https://github.com/mwagnerEE/WagnerControls

添加到@torvin的答案,正如@Dave Huang在评论中提到的,如果你启用了texttrim ="CharacterEllipsis",当你将鼠标悬停在省略号上时,应用程序会崩溃。

我尝试了关于使用TextBox的线程中提到的其他选项,但它似乎真的不是解决方案,因为它不显示“省省号”,也如果文本太长,以适合容器选择文本框的内容“滚动”内部这不是一个TextBlock行为。

我认为最好的解决方案是@torvin的答案,但当悬停在省略号上时,会出现令人讨厌的崩溃。

我知道这并不漂亮,但是在内部订阅/取消订阅未处理的异常和处理异常是我发现解决这个问题的唯一方法,如果有人有更好的解决方案,请分享:)

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);

        this.Loaded += (sender, args) => {
            this.Dispatcher.UnhandledException -= Dispatcher_UnhandledException;
            this.Dispatcher.UnhandledException += Dispatcher_UnhandledException;
        };
        this.Unloaded += (sender, args) => {
            this.Dispatcher.UnhandledException -= Dispatcher_UnhandledException;
        };
    }

    private void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        if (!string.IsNullOrEmpty(e?.Exception?.StackTrace))
        {
            if (e.Exception.StackTrace.Contains("System.Windows.Controls.TextBlock.GetTextPositionFromDistance"))
            {
                e.Handled = true;
            }
        }
    }
}