ChatGPT解决这个技术问题 Extra ChatGPT

有什么方法可以使 WPF 文本块可选?

如何让 TextBlock 的文本可选择?

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

我将尝试使用 RichTextBox 控件来查看是否可行。但是从以前使用richtextbox 的经验来看,涉及的内容要多得多。
您是否考虑过使用 FlowDocumentScrollViewer 以及包含 Paragraphs 和 Runs 的 FlowDocument? - 当我需要可选文本时,这对我来说非常有效,并且每个段落和运行都可以单独设置样式。
在尝试了以下一些解决方法后,FlowDocumentScrollViewer 是前进的方向。它似乎在 RichTextBox 和 TextBlock 之间占据了一个有用的中间地带。
拒绝接受不符合您要求的答案。

Ω
ΩmegaMan

改为使用具有这些设置的 TextBox 使其只读并看起来像 TextBlock 控件。

<TextBox Background="Transparent"
         BorderThickness="0"
         Text="{Binding Text, Mode=OneWay}"
         IsReadOnly="True"
         TextWrapping="Wrap" />

我有一个包含许多文本块/标签的项目,我不能真正将它们变成文本框。我想要做的是,为应用程序级资源添加一个魔法应用到所有样式,这样它应该会影响所有标签/文本块,并将它们的内部文本呈现器作为只读文本框,你知道吗?去做吧?
您可能需要根据您的情况添加 IsTabStop="False"
+1 非常好的解决方案!我添加了一个 Padding="0",因为在我的项目中,文本的底部被剪掉了......也许是因为其他地方的风格。
-1 该问题专门询问如何使文本块可选择。因为他不想失去“内联”属性(文本框没有)。这个“答案”只是建议让文本框看起来像一个文本块。
@AlanLe 为什么当这是您明确表示不想要的答案时,您会接受这个答案?为什么有 147 个无知的人投了赞成票?
t
torvin

这里的所有答案都只是使用 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);
    }
}

我还创建了一个派生自 TextBlockSelectableTextBlock,它采用上述步骤:

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;

与使用任何其他自定义控件的方式相同。例如,参见 stackoverflow.com/a/3768178/332528
是的,它适用于多个 Run 元素,甚至在复制时保留格式
@BillyWilloughby 您的解决方案只是模拟选择。它缺少很多原生选择功能:键盘支持、上下文菜单等。我的解决方案启用了原生选择功能
只要 Hyperlink 不是其中的最后一个内联,当 TextBlock 嵌入 Hyperlink 时,此解决方案似乎确实有效。在内容中添加一个尾随的空 Run 可以解决导致抛出 ExecutionEngineException 的任何潜在问题。
这很棒!除非您在 TextBlock 上有 TextTrimming="CharacterEllipsis" 并且可用宽度不足,如果您将鼠标指针移到 ... 上,它会崩溃并出现 System.ArgumentException “请求的距离超出相关文档的内容。”在 System.Windows.Documents.TextPointer.InitializeOffset(TextPointer 位置,Int32 距离,LogicalDirection 方向) :( 不知道除了将 TextTrimming 设置为 None 之外是否还有其他解决方法。
N
NearHuscarl

我一直找不到任何真正回答这个问题的例子。所有答案都使用了 Textbox 或 RichTextbox。我需要一个允许我使用 TextBlock 的解决方案,这就是我创建的解决方案。

我相信正确的方法是扩展 TextBlock 类。这是我用来扩展 TextBlock 类以允许我选择文本并将其复制到剪贴板的代码。 “sdo”是我在 WPF 中使用的命名空间引用。

WPF 使用扩展类:

xmlns:sdo="clr-namespace:iFaceCaseMain"

<sdo:TextBlockMoo x:Name="txtResults" Background="Black" Margin="5,5,5,5" 
      Foreground="GreenYellow" FontSize="14" FontFamily="Courier New"></TextBlockMoo>

扩展类的代码隐藏:

public partial class TextBlockMoo : TextBlock 
{
    TextPointer StartSelectPosition;
    TextPointer EndSelectPosition;
    public String SelectedText = "";

    public delegate void TextSelectedHandler(string SelectedText);
    public event TextSelectedHandler TextSelected;

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
        base.OnMouseDown(e);
        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);

        TextRange otr = new TextRange(this.ContentStart, this.ContentEnd);
        otr.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.GreenYellow));

        TextRange ntr = new TextRange(StartSelectPosition, EndSelectPosition);
        ntr.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.White));

        SelectedText = ntr.Text;
        if (!(TextSelected == null))
        {
            TextSelected(SelectedText);
        }
    }
}

示例窗口代码:

    public ucExample(IInstanceHost host, ref String WindowTitle, String ApplicationID, String Parameters)
    {
        InitializeComponent();
        /*Used to add selected text to clipboard*/
        this.txtResults.TextSelected += txtResults_TextSelected;
    }

    void txtResults_TextSelected(string SelectedText)
    {
        Clipboard.SetText(SelectedText);
    }

这应该是公认的答案!没有反射黑客,不使用文本框......而且它可以很容易地重构为可重用的行为。非常好,谢谢!
H
Hamlet Hakobyan

为 TextBlock 创建 ControlTemplate 并在其中放置一个带有只读属性集的 TextBox。或者只是使用 TextBox 并将其设置为只读,然后您可以更改 TextBox.Style 使其看起来像 TextBlock。


如何为 TextBlock 设置 ControlTemplate ?找不到房源?
如果您的 TextBlock 中包含内联元素,则此方法将不起作用。如果您有超链接或粗体或斜体文本怎么办? TextBox 不支持这些。
如果您使用内联运行,则不起作用,就像 HaxElit 所问的那样,我不确定您所说的控制模板是什么意思。
-1 TextBlock 没有 ControlTemplate,因为它是 FrameworkElement 的直接子类。另一方面,TextBox 是 Control 的子类。
为什么没有人读书? OP 明确表示需要 TextBlock,而不是 TextBox,因为 TextBlock 支持内联格式,而 TextBox 不支持。为什么像这样完全错误的垃圾答案会得到很多赞成?
m
mbx

将此样式应用于您的 TextBox,就是这样(灵感来自 this article):

<Style x:Key="SelectableTextBlockLikeStyle" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
    <Setter Property="IsReadOnly" Value="True"/>
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Padding" Value="-2,0,0,0"/>
    <!-- The Padding -2,0,0,0 is required because the TextBox
        seems to have an inherent "Padding" of about 2 pixels.
        Without the Padding property,
        the text seems to be 2 pixels to the left
        compared to a TextBlock
    -->
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsMouseOver" Value="False" />
                <Condition Property="IsFocused" Value="False" />
            </MultiTrigger.Conditions>
            <Setter Property="Template">
                <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <TextBlock Text="{TemplateBinding Text}" 
                             FontSize="{TemplateBinding FontSize}"
                             FontStyle="{TemplateBinding FontStyle}"
                             FontFamily="{TemplateBinding FontFamily}"
                             FontWeight="{TemplateBinding FontWeight}"
                             TextWrapping="{TemplateBinding TextWrapping}"
                             Foreground="{DynamicResource NormalText}"
                             Padding="0,0,0,0"
                                       />
                </ControlTemplate>
                </Setter.Value>
            </Setter>
        </MultiTrigger>
    </Style.Triggers>
</Style>

顺便说一句,截至今天,文章的链接似乎已经死了
另一个补充:填充应该是-2,0,-2,0。在 TextBox 内,将创建一个 TextBoxView 控件,其默认 Margin 为 2,0,2,0。不幸的是,你不能重新定义它的样式,因为它被标记为内部的。
似乎没有人能够阅读。 OP 需要一个 TextBlock,而不是像 TextBlock 样式的 TextBox。
B
Bruce

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


我尝试这样做,并且在此过程中必须使用依赖属性使 RichTextBox 可绑定。不幸的是,旧的流文档没有被正确丢弃,内存正在疯狂地泄漏。艾伦,我想知道你是否找到了解决这个问题的方法?
@AlanLe 在这里的所有回复中,这只是实际回答所提问题的两个回复之一......所有其他人都在谈论将 TextBox 设置为看起来像 TextBlock 的样式,而忽略了格式化的需要。奇怪和不幸的是,OP 接受了其中一个非答案,而不是使用 RichTextBox 而不是 TextBox 的正确答案。
J
Jack Pines

根据Windows Dev Center

TextBlock.IsTextSelectionEnabled 属性 [为 Windows 10 上的 UWP 应用更新。对于 Windows 8.x 文章,请参阅存档] 获取或设置一个值,该值指示是否通过用户操作或调用与选择相关的 API 在 TextBlock 中启用文本选择.


不幸的是,与 Win7 不兼容(有时这是必须的)
Amswer 显示不正确。 IsTextSelectionEnabled 仅适用于 UWP,不适用于 WPF - 原始问题确实指定了 WPF。
这很简单,并且可以在 UWP 中使用。谢谢!
感谢您的确认。 @IanJ 的回答让我质疑自己。
S
SimperT

虽然问题确实说“可选择”,但我相信有意的结果是将文本放到剪贴板。这可以通过添加上下文菜单和名为副本的菜单项来轻松优雅地实现,该副本将 Textblock Text 属性值放入剪贴板。反正只是一个想法。


S
Saraf Talukder

TextBlock 没有模板。所以为了实现这一点,我们需要使用一个TextBox,它的样式被改变为一个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>

与其他答案相比,这种方法有什么优势?我没有看到。
我试过这种风格:TextBoxBorder 没有定义。如果您将其注释掉,则可以正常工作
这个示例代码非常棒,它展示了如何获取 TextBlock 的默认颜色。
这很混乱。首先,x:Key,“TextBlockUsingTextBoxStyle”,是向后的;它应该是“TextBoxUsingTextBlockStyle”。其次,OP 已经知道如何像 TextBlock 一样设置 TextBox 的样式,但他反复说他不能使用它,因为他需要内联进行格式化。
R
Richard

有一个替代解决方案可能适用于此 blog post 中的 RichTextBox - 当用户悬停在控件上时,它使用触发器来换出控件模板 - 应该有助于提高性能


您的链接已失效。请在答案中包含所有相关信息,并仅将链接用作引用。
I
Ilya Serbis

new TextBox
{
   Text = text,
   TextAlignment = TextAlignment.Center,
   TextWrapping = TextWrapping.Wrap,
   IsReadOnly = true,
   Background = Brushes.Transparent,
   BorderThickness = new Thickness()
         {
             Top = 0,
             Bottom = 0,
             Left = 0,
             Right = 0
         }
};

这没有帮助。阅读问题以了解 OP 实际想要什么。
S
Shakti

这对我有用。我创建了一个派生自 TextBox 并设置为只读的类 TextBlockEx,并在构造函数中自动换行。

public class TextBlockEx : TextBox
{
    public TextBlockEx()
    {
        base.BorderThickness = new Thickness(0);
        IsReadOnly = true;
        TextWrapping = TextWrapping.Wrap;
        //Background = Brushes.Transparent; // Uncomment to get parent's background color
    }
}

T
Titwan
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;
    }
}

您需要解释一下您从下面的答案中所做的更改。 -1
第 51 行给出: System.ArgumentNullException: 'Value cannot be null。参数名称:position1'
A
Angel T
public MainPage()
{
    this.InitializeComponent();
    ...
    ...
    ...
    //Make Start result text copiable
    TextBlockStatusStart.IsTextSelectionEnabled = true;
}

上面已经回答了这个问题..并且仅适用于 UWP 而不是 WPF
R
Rauland

添加到@torvin 的答案和@Dave Huang 在评论中提到的如果您启用了TextTrimming="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;
            }
        }
    }
}

这些行给了我错误 this.Dispatcher.UnhandledException -= Dispatcher_UnhandledException; this.Dispatcher.UnhandledException += Dispatcher_UnhandledException; DispatcherUnhandledExceptionEventArgs e
N
Nicke Manarin

只需在 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>

https://i.stack.imgur.com/vXYTk.png


M
Michael Wagner

我同意这里的大多数答案都不会创建可选择的文本。 @Billy Willoughby 运行良好,但没有明显的选择提示。我想扩展他的扩展,它可以在选中文本时突出显示文本。它还包含双击和三次单击选择。如果需要,您可以添加带有“复制”的上下文菜单。它使用 Background 属性“突出显示”选择,因此它会覆盖 Run.Background

https://github.com/mwagnerEE/WagnerControls


R
Robert Važan

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

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

这只是使用一个文本框,就像许多年前的许多其他答案一样。