有什么办法可以使WPF文本块成为可选的?


224

我想使显示在Witty(一个开源Twitter客户端)中的文本成为可选择的。当前使用自定义文本块显示它。我需要使用TextBlock,因为我正在使用textblock的内联显示@username和链接并将其设置为超链接。一个常见的请求是能够复制粘贴文本。为此,我需要选择TextBlock。

我试图通过使用只读的TextBox来显示文本,使其看起来像文本块,从而使其正常工作,但是由于TextBox没有内联,因此在我的情况下这是行不通的。换句话说,我无法像使用TextBlock一样单独设置TextBox中文本的样式或格式。

有任何想法吗?


1
我将尝试使用RichTextBox控件查看是否可以正常工作。但是从以前的经验来看,使用Richtextbox涉及的更多。
艾伦·勒

您是否考虑过将FlowDocumentScrollViewer与包含段落和运行的FlowDocument一起使用?-当我需要可选文本时,这对我来说效果很好,并且每个段落和运行都可以分别设置样式。
BrainSlugs83

尝试了以下一些解决方法后,FlowDocumentScrollViewer才是前进的道路。它似乎在RichTextBox和TextBlock之间占据了有用的中间位置。
汤姆·马金

接受不符合您要求的答案的否决票。
Blechdose

Answers:


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

6
我有一个包含许多TextBlocks / Label的项目,我无法真正将它们转换为TextBoxes。我要做的是,将神奇的“应用于所有样式”添加到应用程序级资源,这样它应该会影响所有Label / TextBlock,并将其内部文本演示器作为只读TextBox出现,您是否知道去做吧?
Shimmy Weitzhandler 2011年

5
您可能要根据情况添加IsTabStop =“ False”
Karsten

1
+1非常好的解决方案!我添加了Padding =“ 0”,因为在我的项目中,文本的底部被剪掉了……也许是因为其他地方的样式。
13年

123
-1该问题专门询问如何使文本块可选。因为他不想丢失“ Inlines”属性(文本框没有该属性)。这个“答案”只是建议使文本框看起来像文本块。
2014年

19
@AlanLe当您明确表示自己不想这么做时,为什么您会接受此答案?为什么有147个无知的人支持它?
Jim Balter

66

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

经过数小时的研究和阅读WPF源代码,我发现了一种为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);
    }
}

我还创建了一个SelectableTextBlockTextBlock上面导出的内容,该步骤遵循上述步骤:

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使用此代码的反射等效项来分离a :

_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;

1
您如何在另一个应该包含它的xaml中使用SelectableTextBlock类?
Yoav Feuerstein

1
使用其他自定义控件的方式相同。参见例如stackoverflow.com/a/3768178/332528
torvin

3
@BillyWilloughby您的解决方案仅模拟选择。它缺少许多本机选择功能:键盘支持,上下文菜单等。我的解决方案启用了本机选择功能
torvin

3
似乎只要嵌入了s,只要它不是最后一个内联,此解决方案就可以工作。在内容中添加尾随空格可解决导致抛出问题的根本问题。TextBlockHyperlinkHyperlinkRunExecutionEngineException
安东·泰克

2
这很棒!除非您在TextTrimming="CharacterEllipsis"TextBlock并且可用宽度不足,否则将鼠标指针移到…上时,它将崩溃,并发生System.ArgumentException“请求的距离超出了关联文档的内容。” 在System.Windows.Documents.TextPointer.InitializeOffset(TextPointer位置,距离的Int32,LogicalDirection方向):(不知道是否有其他的解决方法,而不是假的TextTrimming设置为无。
戴夫黄

32

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

1
这应该是公认的答案!没有反射技巧,不使用TextBox ...并且可以很容易地将其重构为可重用的行为。很好,谢谢!
Thomas Levesque

19

将此样式应用于您的TextBox就是这样(从本文启发):

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

1
顺便说一句,直到今天,与文章的链接似乎已经失效
superjos 2015年

2
另一个补充:填充应为-2,0,-2,0。在TextBox内,创建一个TextBoxView控件,其默认Margin为2,0,2,0。不幸的是,您无法重新定义其样式,因为它已标记为内部。
fdub

11
似乎没有人能够阅读。OP需要一个TextBlock,而不是样式像TextBlock的TextBox。
Jim Balter

18

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


11
如何设置TextBlock的ControlTemplate?我找不到物业?
HaxElit 2010年

18
如果您的TextBlock中包含内联元素,则此方法将不起作用。如果您有超链接或粗体或斜体文本行怎么办?TextBox不支持这些。
dthrasher 2011年

1
如果您使用内联运行,则不起作用,就像HaxElit所问的那样,我不确定控制模板的含义。
里奇·梅尔顿

7
-1 TextBlock没有ControlTemplate,因为它是FrameworkElement的直接子类。另一方面,TextBox是Control的子类。
reSPAWNed

5
为什么没有人读?OP明确表示需要TextBlock,而不是TextBox,因为TextBlock支持内联格式,而TextBox不支持。为什么像这样的完全错误的垃圾答案会得到无数的赞誉?
Jim Balter

10

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


1
我尝试这样做,并且在此过程中必须使RichTextBox与依赖项属性可绑定。不幸的是,旧的流文档没有被正确丢弃,并且内存正在疯狂地泄漏。艾伦,我想知道您是否找到解决方法?
John Noonan

@AlanLe在这里所有答复中,这只是回答问题的两个答复之一……其他所有人都在谈论将TextBox设置为类似于TextBlock的样式,而忽略了格式化的需要。奇怪的是,不幸的是,OP接受了这些非答案之一,而不是使用RichTextBox而不是TextBox的正确答案。
Jim Balter

9

根据Windows开发中心

TextBlock.IsTextSelectionEnabled属性

[已针对Windows 10上的UWP应用更新。有关Windows 8.x的文章,请参见档案 ]

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


5
不幸的是,与Win7不兼容(有时是必须的)
Yury Schkatula

24
答案似乎不正确。IsTextSelectionEnabled仅适用于UWP,不适用于WPF-原始问题确实指定了WPF。
海鹦

6

虽然问题的确是“可选的”,但我相信故意的结果是将文本发送到剪贴板。这可以通过添加上下文菜单和名为copy的菜单项轻松而优雅地实现,该菜单项将Textblock Text属性值放入剪贴板。无论如何只是一个想法。


4

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>

与其他答案相比,这种方法有什么优势?我没看到。
surfen 2011年

我尝试了这种样式:TextBoxBorder未定义。如果您将其注释掉,它会很好
sthiers,2014年

此示例代码非常出色,它显示了如何获取TextBlock的默认颜色。
康坦戈2015年

1
这很混乱。首先,x:Key“ TextBlockUsingTextBoxStyle”向后;它应该是“ TextBoxUsingTextBlockStyle”。其次,OP已经知道如何像TextBlock一样设置TextBox的样式,但是反复说他不能使用它,因为他需要内联的格式。
Jim Balter

2

有一种替代解决方案可能适用于此博客文章中提到的RichTextBox- 当用户将鼠标悬停在控件上时,它使用触发器来换出控件模板-应该有助于提高性能


1
您的链接已死。请在答案中包括所有相关信息,并仅将链接用作引文。
Jim Balter

1

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


1
这没有帮助。阅读问题以了解OP的实际需求。
Jim Balter

1

添加到@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;
            }
        }
    }
}


0
public MainPage()
{
    this.InitializeComponent();
    ...
    ...
    ...
    //Make Start result text copiable
    TextBlockStatusStart.IsTextSelectionEnabled = true;
}

-1
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
您需要从以下答案中解释您所做的更改。-1
亚历克斯·霍普·奥康纳

第51行给出:System.ArgumentNullException:'值不能为null。参数名:位置1'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.