WPFテキストブロックを選択可能にする方法はありますか?


224

オープンソースのTwitterクライアントであるWittyに表示されるテキストを選択可能にしたい。現在、カスタムテキストブロックを使用して表示されています。@usernameとリンクをハイパーリンクとして表示およびフォーマットするためにテキストブロックのインラインを操作しているため、TextBlockを使用する必要があります。頻繁に要求されるのは、テキストをコピーして貼り付けることができるようにすることです。そのためには、TextBlockを選択可能にする必要があります。

テキストブロックのようにスタイル設定された読み取り専用のTextBoxを使用してテキストを表示することでテキストを表示しようとしましたが、TextBoxにインラインがないため、私の場合は機能しません。つまり、TextBlockの場合のように、TextBox内のテキストを個別にスタイル設定またはフォーマットすることはできません。

何か案は?


1
RichTextBoxコントロールを使用して、機能するかどうかを確認します。しかし、以前の経験から、richtextboxでの作業ははるかに複雑です。
アラン・ル・

段落と実行を含むFlowDocumentでFlowDocumentScrollViewerを使用することを考えましたか?-これは、選択可能なテキストが必要な場合に非常にうまく機能し、各段落と段落のスタイルを個別に設定できます。
BrainSlugs83

以下のいくつかの回避策を試した結果、FlowDocumentScrollViewerが前進しました。これは、RichTextBoxとTextBlockの間の便利な中間点を占めるようです。
トム・マキン

要件に合わない回答を受け入れるには反対票を投じます。
Blechdose

回答:


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

6
多くのTextBlocks / Labelsを含むプロジェクトがありますが、それらを実際にTextBoxesに変換することはできません。私がしたいことは、すべてのLabel / TextBlockに影響を与えるようにアプリレベルのリソースに魔法のapply-to-allスタイルを追加し、それらの内部テキストプレゼンターを読み取り専用のTextBoxにすることです。それをするために?
Shimmy Weitzhandler、2011年

5
状況に応じてIsTabStop = "False"を追加することもできます
Karsten

1
+1とても良い解決策です!私のプロジェクトでは、テキストの下部が切り取られていたので、Padding = "0"を追加しました...多分どこか他のスタイルのために。
2013

123
-1質問では、テキストブロックを選択可能にする方法を具体的に尋ねます。彼は "textlines"にはない "Inlines"プロパティを失いたくないからです。この「答え」は、テキストボックスをテキストブロックのように見せることを示唆しています。
00jt 2014年

19
@AlanLeなぜあなたが明示的にあなたが望まないと言ったのに、あなたはこの答えを受け入れたのですか そして、なぜ147人の無知な人々がそれを賛成したのですか?
ジムバルター2016

66

ここでのすべての回答はTextBox、テキスト選択を手動で使用するか実装しようとしているため、パフォーマンスが低下したり、ネイティブでTextBoxはない動作になります(でキャレットが点滅、手動実装ではキーボードがサポートされていないなど)。

WPFソースコードを何時間も調べて読んだ後、代わりに、TextBlockコントロール(または実際には他のすべてのコントロール)のネイティブWPFテキスト選択を有効にする方法を発見しました。テキスト選択に関する機能のほとんどは、System.Windows.Documents.TextEditorシステムクラスに実装されています。

コントロールのテキスト選択を有効にするには、2つのことを行う必要があります。

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

上記の手順を実行SelectableTextBlockするからも派生を作成しましたTextBlock

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;

1
SelectableTextBlockクラスを、それを含む別のxaml内でどのように使用しますか?
Yoav Feuerstein、2017

1
他のカスタムコントロールを使用するのと同じ方法。たとえば、stackoverflow.com
a / 3768178/332528を

3
@BillyWilloughbyあなたのソリューションは単に選択をエミュレートします。キーボードのサポート、コンテキストメニューなど、多くのネイティブ選択機能が欠けています。私のソリューションは、ネイティブ選択機能を有効にします
torvin

3
がを埋め込んだ場合、が最後のインラインでない限り、このソリューション機能するようです。末尾の空をコンテンツに追加すると、スローされる原因となる根本的な問題はすべて修正されます。TextBlockHyperlinkHyperlinkRunExecutionEngineException
Anton Tykhyy 2018年

2
これは素晴らしい!TextTrimming="CharacterEllipsis"上にTextBlockあり、利用可能な幅が不十分である場合を除いて、マウスポインターを…の上に移動すると、System.ArgumentException "要求された距離が関連ドキュメントのコンテンツの外にあります"でクラッシュします。System.Windows.Documents.TextPointer.InitializeOffset(TextPointer位置、Int32距離、LogicalDirection方向)で:(TextTrimmingをNoneに設定したままにする以外の回避策があるかどうかわからない
Dave Huang

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を使用せず、リフレクションハッキングはありません...そして、再利用可能な動作に簡単にリファクタリングできます。とても素敵です、ありがとう!
トーマスレベスク

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 '10 / 10/20

2
別の追加:パディングは-2,0、-2,0でなければなりません。TextBox内に、デフォルトのマージンが2,0,2,0のTextBoxViewコントロールが作成されます。残念ながら、スタイルは内部としてマークされているため、再定義できません。
fdub

11
誰も読めないようです。OPには、TextBlockのようなスタイルのTextBoxではなく、TextBlockが必要です。
ジムバルター2016

18

TextBlockのControlTemplateを作成し、readBoxプロパティを設定してTextBoxを内部に配置します。または、TextBoxを使用して読み取り専用にしてから、TextBox.Styleを変更してTextBlockのようにすることができます。


11
TextBlockのControlTemplateをどのように設定しますか?プロパティが見つかりませんか?
HaxElit 2010年

18
TextBlock内にインライン要素がある場合、このアプローチは機能しません。ハイパーリンクや太字や斜体のテキストがある場合はどうなりますか?TextBoxはこれらをサポートしていません。
dthrasher

1
インライン実行を使用している場合は機能しません。また、HaxElitに尋ねられたように、コントロールテンプレートの意味がわかりません。
リッチメルトン

7
-1 TextBlockは、FrameworkElementの直接のサブクラスであるため、ControlTemplateを持ちません。一方、TextBoxはControlのサブクラスです。
2013

5
なぜ誰も読むことができないのですか?OPは、TextBlockはインラインフォーマットをサポートし、TextBoxはサポートしないため、TextBoxではなくTextBlockが必要であると明示的に述べています。このように完全に間違ったゴミの答えがなぜ多数の賛成票を獲得するのですか?
ジムバルター2016

10

TextBlockを選択可能にすることができるかどうかはわかりませんが、RichTextBoxを使用することもできます。これは、提案されたTextBoxに似ていますが、必要なフォーマットをサポートしています。


1
私はこれを試してみましたが、その過程で、RichTextBoxを依存関係プロパティでバインド可能にする必要がありました。残念ながら、古いフロードキュメントは適切に破棄されておらず、メモリが狂ったようにリークしています。アラン、これを回避する方法を見つけたのかな?
ジョン・ヌーナン、

@AlanLeここでのすべての応答のうち、これは実際に尋ねられた質問に実際に答える2つのうちの1つにすぎません。OPがTextBoxではなくRichTextBoxを使用するための正解ではなく、これらの非回答の1つを受け入れたのは奇妙で残念です。
ジムバルター2016

9

Windows Dev Centerによると:

TextBlock.IsTextSelectionEnabledプロパティ

[Windows 10のUWPアプリ用に更新されました。Windows8.xの記事については、アーカイブを参照してください]

ユーザーアクションまたは選択関連のAPIの呼び出しによって、TextBlockでテキスト選択が有効かどうかを示す値を取得または設定します。


5
残念ながら、Win7との互換性はありません(必要な場合もあります)
Yury Schkatula

24
Amswerが正しく表示されません。IsTextSelectionEnabledはUWP専用であり、WPFではありません-元の質問ではWPFが指定されていました。
Puffin 2017年

6

質問には「選択可能」とありますが、意図的な結果はテキストをクリップボードに移動することです。これは、コンテキストメニューと、Textblock Textプロパティの値をクリップボードに配置するcopyというメニュー項目を追加することで、簡単かつエレガントに実現できます。とにかくただのアイデア。


4

TextBlockにはテンプレートがありません。したがって、これを実現するには、スタイルがtextBlockとして動作するように変更されたTextBoxを使用する必要があります。

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

このアプローチには、他の回答と比較してどのような利点がありますか?何も見えません。
2011

私はこのスタイルを試しました:TextBoxBorderが定義されていません。コメントアウトすると、問題なく動作します
sthiers 2014年

このサンプルコードは優れており、TextBlockのデフォルトの色を取得する方法を示しています。
Contango、2015

1
これはかなり混乱しています。まず、x:Key "TextBlockUsingTextBoxStyle"は逆です。「TextBoxUsingTextBlockStyle」である必要があります。第二に、OPはすでにTextBlockのようなTextBoxのスタイルを設定する方法を知っていましたが、書式設定にインラインが必要だったのでそれを使用できないと繰り返し言いました。
ジムバルター2016

2

このブログ投稿で執筆されたRichTextBoxに適応できる可能性のある代替ソリューションがあります-トリガーを使用して、コントロールがテンプレートに置き換わったときにコントロールテンプレートをスワップアウトしました-パフォーマンスに役立ちます


1
あなたのリンクは死んでいます。回答にすべての関連情報を含め、リンクは引用としてのみ使用してください。
ジムバルター2016

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が実際に何を望んでいるかを確認してください。
ジムバルター2016

1

@torvinの回答に追加しTextTrimming="CharacterEllipsis"、省略記号の上にカーソルを置くとアプリケーションがクラッシュした場合に@Dave Huangがコメントで述べたように。

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

オープンソースコントロールライブラリにSelectableTextBlockを実装しました。次のように使用できます。

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

4
これは、何年も前からの他の多くの回答と同様に、TextBoxを使用するだけです。
クリス

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にすることはできません。パラメータ名:position1 '
ロール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.