260

How to allow TextBlock's text to be selectable?

I tried to get it to work by displaying the text using a read-only TextBox styled to look like a textblock but this will not work in my case because a TextBox does not have inlines. In other words, how to make it selectable?

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
Alan Le
  • 8,683
  • 7
  • 36
  • 31
  • 2
    I'll try using the RichTextBox control to see if that'll work. But from prior experience working with the richtextbox is much more involved. – Alan Le Sep 25 '08 at 22:06
  • 1
    Have you thought about using a FlowDocumentScrollViewer, with a FlowDocument containing Paragraphs and Runs? -- This works pretty well for me when I need selectable text, and each Paragraph and Run can be styled separately. – BrainSlugs83 Jan 25 '15 at 04:58
  • 1
    Having tried some of the workarounds below, FlowDocumentScrollViewer was the way forward. It seems to occupy a useful middle ground between RichTextBox and TextBlock. – Tom Makin May 19 '16 at 15:04
  • 2
    down vote for accepting an answer that does not fit your requirements. – Welcor Mar 02 '20 at 07:32

20 Answers20

273

Use a TextBox with these settings instead to make it read only and to look like a TextBlock control.

<TextBox Background="Transparent"
         BorderThickness="0"
         Text="{Binding Text, Mode=OneWay}"
         IsReadOnly="True"
         TextWrapping="Wrap" />
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
MSB
  • 3,090
  • 2
  • 16
  • 2
  • 9
    I have a project that contains many TextBlocks/Labels, I can't really turn them into TextBoxes. What I do want to do is, add a magic apply-to-all Style to the app-level resource so it should affect all the Label/TextBlock, and make their internal text presenter as a readonly TextBox, do you know of any way to do it? – Shimmy Weitzhandler Jan 18 '11 at 08:34
  • 7
    You may want to add IsTabStop="False" depending on your situation – Karsten Oct 25 '12 at 11:51
  • 2
    +1 Very nice solution! I added a Padding="0", since in my project the bottom of the text was cut of... Perhaps because of a style somewhere else. – reSPAWNed Aug 29 '13 at 12:11
  • 1
    Only problem I have with this is that you lose TextTrimming. – Mike Fuchs Aug 21 '14 at 12:07
  • 152
    -1 The Question specifically asks how to make a textblock selectable. Because he doesn't want to lose the "Inlines" property (which textBoxes do not have). This 'answer' is just suggesting to make a textbox look like a textblock. – 00jt Nov 14 '14 at 15:07
  • 28
    @AlanLe Why did you accept this answer when it's what you explicitly said you didn't want? And why did 147 clueless people upvote it? – Jim Balter Apr 15 '16 at 02:27
  • 1
    @Ray: Why? What happens if you don't? – Heinzi Oct 18 '16 at 14:39
  • 2
    I have added a solution below that allows a textblock to work as requested in the original solution, I agree, why so many upvotes for a solution that does exact what he said he didn't want to do? – Billy Willoughby Oct 02 '17 at 15:47
  • 3
    Agree with @00jt - question is about TextBlock NOT TextBox. Shouldn't have been selected as the answer in my opinion. – Bertie Mar 21 '18 at 16:08
  • 2
    Putting this type of TextBox right next to a TextBlock, I notice the TextBox has a little "margin" even though the Margin property is 0. Do you know why or how to get rid of it? – Kyle Delaney Jun 26 '18 at 18:37
  • 2
    @KyleDelaney It's probably the `Padding`. A little late, but maybe it helps someone – Biesi Aug 07 '19 at 09:37
  • A shorter version of this without a .xaml is here: https://stackoverflow.com/a/68106968/741147 – Gsv Jun 23 '21 at 21:08
94

All the answers here are just using a TextBox or trying to implement text selection manually, which leads to poor performance or non-native behaviour (blinking caret in TextBox, no keyboard support in manual implementations etc.)

After hours of digging around and reading the WPF source code, I instead discovered a way of enabling the native WPF text selection for TextBlock controls (or really any other controls). Most of the functionality around text selection is implemented in System.Windows.Documents.TextEditor system class.

To enable text selection for your control you need to do two things:

  1. Call TextEditor.RegisterCommandHandlers() once to register class event handlers

  2. Create an instance of TextEditor for each instance of your class and pass the underlying instance of your System.Windows.Documents.ITextContainer to it

There's also a requirement that your control's Focusable property is set to True.

This is it! Sounds easy, but unfortunately TextEditor class is marked as internal. So I had to write a reflection wrapper around it:

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

I also created a SelectableTextBlock derived from TextBlock that takes the steps noted above:

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

Another option would be to create an attached property for TextBlock to enable text selection on demand. In this case, to disable the selection again, one needs to detach a TextEditor by using the reflection equivalent of this code:

_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;
torvin
  • 6,515
  • 1
  • 37
  • 52
  • 1
    how would you use the SelectableTextBlock class within another xaml that should contain it? – Yoav Feuerstein Aug 15 '17 at 12:57
  • 3
    the same way you would use any other custom control. see https://stackoverflow.com/a/3768178/332528 for example – torvin Aug 16 '17 at 04:42
  • 1
    I've tried something similar, but then couldn't reference to it from another xaml. Also, just to make sure - in your proposed solution, can the user select text across multiple Run elements/objects that are inside the TextBlock? – Yoav Feuerstein Aug 16 '17 at 06:37
  • 2
    Yes, it works across multiple `Run` elements and even preserves the formatting when copied – torvin Aug 16 '17 at 07:40
  • 1
    Overall, this works quite well. However, do not use this if you've embedded a `Hyperlink` in the `TextBlock`...it will throw an `ExecutionEngineException`. – DonBoitnott Apr 09 '18 at 19:41
  • 1
    What is the improvement over the answer I provided extending the TextBlock class? It looks more complex and I'm trying to understand what makes it better? I see several comments saying "finally using a TextBlock", but it's extending it as well. Please help me understand. Thanks; Billy – Billy Willoughby Jun 04 '18 at 12:17
  • 4
    @BillyWilloughby your solution just emulates selection. It lacks a lot of native selection features: keyboard support, context menu etc. My solution enables the native selection feature – torvin Jun 04 '18 at 23:16
  • A word of caution: while this does work quite well under normal circumstances, it can cause nasty crashes when used within other controls, or when controls are embedded within it. – DonBoitnott Jun 12 '18 at 18:51
  • 4
    It seems that this solution _does_ work when the `TextBlock` has embedded `Hyperlink`s as long as the `Hyperlink` is not the last inline in it. Adding a trailing empty `Run` to the content fixes whatever the underlying problem is that results in the `ExecutionEngineException` being thrown. – Anton Tykhyy Oct 06 '18 at 10:05
  • 3
    This is great! Except if you have `TextTrimming="CharacterEllipsis"` on the `TextBlock` and the available width is insufficient, if you move the mouse pointer over the …, it crashes with System.ArgumentException "Requested distance is outside the content of the associated document." at System.Windows.Documents.TextPointer.InitializeOffset(TextPointer position, Int32 distance, LogicalDirection direction) :( Don't know if there's a workaround other than to leave TextTrimming set to None. – Dave Huang Feb 26 '19 at 00:15
  • If you set the `_editor.TextContainer.TextView` to `null`, enabling the selection again will not work. Enabling/disabling works without this line. – GregorMohorko Feb 19 '20 at 10:42
46

I have been unable to find any example of really answering the question. All the answers used a Textbox or RichTextbox. I needed a solution that allowed me to use a TextBlock, and this is the solution I created.

I believe the correct way to do this is to extend the TextBlock class. This is the code I used to extend the TextBlock class to allow me to select the text and copy it to clipboard. "sdo" is the namespace reference I used in the WPF.

WPF Using Extended Class:

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>

Code Behind for Extended Class:

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

Example Window Code:

    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);
    }
NearHuscarl
  • 66,950
  • 18
  • 261
  • 230
Billy Willoughby
  • 806
  • 11
  • 15
  • 4
    This should be the accepted answer! No reflection hacks, not using a TextBox... And it can be easily refactored into a reusable behavior. Very nice, thanks! – Thomas Levesque Jan 07 '20 at 11:22
19

Create ControlTemplate for the TextBlock and put a TextBox inside with readonly property set. Or just use TextBox and make it readonly, then you can change the TextBox.Style to make it looks like TextBlock.

Hamlet Hakobyan
  • 32,965
  • 6
  • 52
  • 68
Jobi Joy
  • 49,102
  • 20
  • 108
  • 119
  • 12
    How do you set the ControlTemplate for a TextBlock ? I can't find the property ? – HaxElit Jan 14 '10 at 15:39
  • You can also set the style of the border it should look like a TextBlock. I think that a TextBox costs even less performance than a TextBlock, I think. – Shimmy Weitzhandler Feb 15 '10 at 10:49
  • 22
    This approach won't work if your TextBlock has inline elements within it. What if you've got hyperlinks or runs of bold or italic text? TextBox doesn't support these. – dthrasher Mar 02 '11 at 23:11
  • 2
    Doesn't work if you are using inline runs, and like HaxElit asked, I'm not sure what you mean by control template. – Ritch Melton Aug 23 '11 at 22:33
  • 8
    -1 TextBlock does not have a ControlTemplate because it is a direct subclass of FrameworkElement. TextBox on the other hand is a subclass of Control. – reSPAWNed Aug 29 '13 at 10:21
  • 6
    Why can't anyone read? The OP explicitly said a TextBlock is needed, not a TextBox, because TextBlock supports inline formatting and TextBox doesn't. Why do completely wrong garbage answers like this get numerous upvotes? – Jim Balter Apr 15 '16 at 02:32
18

Apply this style to your TextBox and that's it (inspired from 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>
mbx
  • 6,292
  • 6
  • 58
  • 91
jdearana
  • 1,089
  • 12
  • 17
  • 2
    BTW as of today, the link to article seems dead – superjos Oct 20 '15 at 18:49
  • 3
    Another addition: Padding should be -2,0,-2,0. Inside the TextBox, a TextBoxView control is created that has a default Margin of 2,0,2,0. Unfortunately, you can't re-define its Style because it's marked internal. – fdub Dec 17 '15 at 11:14
  • 20
    No one seems able to read. The OP needs a TextBlock, not a TextBox styled like a TextBlock. – Jim Balter Apr 15 '16 at 02:35
11

I'm not sure if you can make a TextBlock selectable, but another option would be to use a RichTextBox - it is like a TextBox as you suggested, but supports the formatting you want.

Bruce
  • 8,202
  • 6
  • 37
  • 49
  • 1
    I tried doing this, and in the process had to make the RichTextBox bindable with a dependency property. Unfortunately the old flowdocuments aren't being discarded properly and memory is leaking like crazy. Alan, I wonder if you found a way around this? – John Noonan Apr 24 '09 at 05:59
  • @AlanLe Of all the responses here, this is only one of two that actually answers the question asked ... all the others talk about styling a TextBox to look like a TextBlock, while ignoring the need for formatting. It's bizarre, and unfortunate, that the OP accepted one of those non-answers, instead of the correct answer to use RichTextBox rather than TextBox. – Jim Balter Apr 15 '16 at 03:37
9

According to Windows Dev Center:

TextBlock.IsTextSelectionEnabled property

[ Updated for UWP apps on Windows 10. For Windows 8.x articles, see the archive ]

Gets or sets a value that indicates whether text selection is enabled in the TextBlock, either through user action or calling selection-related API.

Jack Pines
  • 473
  • 4
  • 13
7

While the question does say 'Selectable' I believe the intentional results is to get the text to the clipboard. This can easily and elegantly be achieved by adding a Context Menu and menu item called copy that puts the Textblock Text property value in clipboard. Just an idea anyway.

SimperT
  • 2,837
  • 2
  • 15
  • 19
2

TextBlock does not have a template. So inorder to achieve this, we need to use a TextBox whose style is changed to behave as a 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>
Saraf Talukder
  • 376
  • 2
  • 12
  • What advantages does this approach offer compared to other answers? I don't see any. – surfen Dec 22 '11 at 01:09
  • I tried this style: TextBoxBorder is not defined. If you comment it out, it works fine – sthiers May 23 '14 at 07:58
  • This example code is excellent, it shows how to get the default color for a TextBlock. – Contango Feb 06 '15 at 15:54
  • 1
    This is quite confused. First, the x:Key, "TextBlockUsingTextBoxStyle", is backwards; it should be "TextBoxUsingTextBlockStyle". Second, the OP already knew how to style a TextBox like a TextBlock, but said repeatedly that he couldn't use that because he needed inlines for formatting. – Jim Balter Apr 15 '16 at 03:53
1

There is an alternative solution that might be adaptable to the RichTextBox oultined in this blog post - it used a trigger to swap out the control template when the use hovers over the control - should help with performance

Richard
  • 1,804
  • 16
  • 22
  • 1
    Your link is dead. Please include all relevant information within an answer and use links only as citations. – Jim Balter Apr 15 '16 at 03:42
1

Here is what worked for me. I created a class TextBlockEx that is derived from TextBox and is set read-only, and text wrap in the constructor.

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
    }
}
Gsv
  • 121
  • 1
  • 10
0
Really nice and easy solution, exactly what I wanted !

I bring some small modifications

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;
    }
}
Titwan
  • 729
  • 5
  • 6
0

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

Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
0
public MainPage()
{
    this.InitializeComponent();
    ...
    ...
    ...
    //Make Start result text copiable
    TextBlockStatusStart.IsTextSelectionEnabled = true;
}
Angel T
  • 182
  • 2
  • 7
0

Adding to @torvin's answer and as @Dave Huang mentioned in the comments if you have TextTrimming="CharacterEllipsis" enabled the application crashes when you hover over the ellipsis.

I tried other options mentioned in the thread about using a TextBox but it really doesn't seem to be the solution either as it doesn't show the 'ellipsis' and also if the text is too long to fit the container selecting the content of the textbox 'scrolls' internally which isn't a TextBlock behaviour.

I think the best solution is @torvin's answer but has the nasty crash when hovering over the ellipsis.

I know it isn't pretty, but subscribing/unsubscribing internally to unhandled exceptions and handling the exception was the only way I found of solving this problem, please share if somebody has a better solution :)

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;
            }
        }
    }
}
Rauland
  • 2,944
  • 5
  • 34
  • 44
  • These lines are giving me errors this.Dispatcher.UnhandledException -= Dispatcher_UnhandledException; this.Dispatcher.UnhandledException += Dispatcher_UnhandledException; DispatcherUnhandledExceptionEventArgs e – JaredCS Jul 21 '21 at 18:39
0

Just use a FlowDocument inside a FlowDocumentScrollViewer, passing your inlines to the element. You can control the style of the element, in my case I added a small border.

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

enter image description here

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
0

I agree most answers here do not create a selectable TextBlock. @Billy Willoughby's worked well however it didn't have a visible cue for selection. I'd like to extend his extension which can highlight text as it is selected. It also incorporates double and triple click selection. You can add a context menu with a "Copy" if needed. It uses the Background property to "highlight" the selection so it is limited in that it will overwrite Run.Background

https://github.com/mwagnerEE/WagnerControls

Michael Wagner
  • 314
  • 1
  • 12
0

Added Selection & SelectionChanged Event to torvin's code

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;
}
altair
  • 103
  • 1
  • 6
0

My solution uses the same principal as @Billy Willoughby, using mouse events for selection. What it adds is better access to the selection:

  • SelectionStart property
  • SelectionLength property
  • SelectedText property
  • SelectionChanging event (while selection in progress)
  • SelectionChanged event (once mouse button released)

These properties (which are not read-only) allow it to be a nearly drop-in replacment for a read-only TextBox. It does highlight the selection while in progress.

As part of the implementation, it adds some extension methods to TextBlock: GetPositionFromIndex() and GetIndexFromPosition(), which convert between a TextPointer and a character index.

I call it SelectableTextBlock, and you can find it in a GitHub Gist at https://gist.github.com/TimPaterson.

DosMan
  • 31
  • 4
  • I have tried your code. But the color doesn't change back to its original color when the selection cancelled. – Wachid Susilo Aug 06 '23 at 21:34
  • The method SetNormal() is called from several places to restore the normal colors when the selection is removed. Using the comment section of the Gist would be easier for troubleshooting this with you. – DosMan Aug 24 '23 at 17:20
-1

I've implemented SelectableTextBlock in my opensource controls library. You can use it like this:

<jc:SelectableTextBlock Text="Some text" />
Robert Važan
  • 3,399
  • 2
  • 25
  • 31