【问题标题】:Any way to make a WPF textblock selectable?有什么方法可以使 WPF 文本块可选?
【发布时间】:2008-09-25 21:56:51
【问题描述】:

如何让TextBlock的文字可选择?

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

【问题讨论】:

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

标签: wpf xaml textbox textblock


【解决方案1】:

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

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

【讨论】:

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

这里的所有答案都只是使用TextBox 或尝试手动实现文本选择,这会导致性能不佳或非本地行为(TextBox 中的插入符号闪烁,手动实现中不支持键盘等)

经过数小时的挖掘和阅读WPF source code,我发现了一种为TextBlock 控件(或实际上任何其他控件)启用本机WPF 文本选择的方法。围绕文本选择的大部分功能都在System.Windows.Documents.TextEditor 系统类中实现。

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

  1. 拨打TextEditor.RegisterCommandHandlers()一次注册课程 事件处理程序

  2. 为您的类的每个实例创建一个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;

【讨论】:

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

我一直找不到任何真正回答问题的例子。所有答案都使用了 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);
    }

【讨论】:

  • 这应该是公认的答案!没有反射黑客,不使用文本框......而且它可以很容易地重构为可重用的行为。非常好,谢谢!
【解决方案4】:

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

【讨论】:

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

将此样式应用于您的 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。
【解决方案6】:

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

【讨论】:

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

根据Windows Dev Center

TextBlock.IsTextSelectionEnabled 属性

[ 针对 Windows 10 上的 UWP 应用进行了更新。有关 Windows 8.x 文章,请参阅 archive]

获取或设置一个值,该值指示是否启用文本选择 在TextBlock 中,通过用户操作或调用 选择相关的 API。

【讨论】:

  • 很遗憾,与 Win7 不兼容(有时这是必须的)
  • Amswer 显示不正确。 IsTextSelectionEnabled 仅适用于 UWP,不适用于 WPF - 原始问题确实指定了 WPF。
【解决方案8】:

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

【讨论】:

    【解决方案9】:

    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 的样式,但他反复说他不能使用它,因为他需要内联进行格式化。
    【解决方案10】:

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

    【讨论】:

    • 您的链接已失效。请在答案中包含所有相关信息,并仅将链接用作引用。
    【解决方案11】:
    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 真正想要什么。
    【解决方案12】:

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

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

    【讨论】:

    • 这只是使用一个文本框,就像许多年前的许多其他答案一样。
    【解决方案13】:
    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: '值不能为空。参数名称:position1'
    【解决方案14】:
    public MainPage()
    {
        this.InitializeComponent();
        ...
        ...
        ...
        //Make Start result text copiable
        TextBlockStatusStart.IsTextSelectionEnabled = true;
    }
    

    【讨论】:

    • 上面已经回答了这个问题..并且仅适用于 UWP 而不是 WPF
    【解决方案15】:

    添加到@torvin 的答案和@Dave Huang 在 cmets 中提到的如果您启用了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
    【解决方案16】:

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

    【讨论】:

      【解决方案17】:

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

      https://github.com/mwagnerEE/WagnerControls

      【讨论】:

        【解决方案18】:

        这对我有用。我创建了一个派生自 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
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-09
          • 1970-01-01
          • 2015-08-22
          • 1970-01-01
          相关资源
          最近更新 更多