web-dev-qa-db-ja.com

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

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

私はテキストブロックのように見えるようにスタイル設定された読み取り専用のTextBoxを使用してテキストを表示することでそれを動作させようとしましたが、TextBoxにはインラインがないため、これは動作しません。つまり、TextBlockのようにTextBox内のテキストを個別にスタイル設定または書式設定することはできません。

何か案は?

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

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

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

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

  1. TextEditor.RegisterCommandHandlers()を1回呼び出して、クラスイベントハンドラーを登録します

  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;
48
torvin

本当に質問に答える例が見つかりませんでした。すべての答えは、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);
    }
28

このスタイルを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>
20
juanjo.arana

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

19
Jobi Joy

Windows Dev Center によると:

TextBlock.IsTextSelectionEnabledプロパティ

[Windows 10のUWPアプリ用に更新。Windows8.xの記事については、 archive を参照してください。

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

9
Jack Pines

TextBlockを選択可能にするかどうかはわかりませんが、別のオプションはRichTextBoxを使用することです。これは、提案されたTextBoxのようなものですが、必要な書式設定をサポートします。

9
Bruce

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>
4
Saraf Talukder

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

4
SimperT

この ブログ投稿 で説明されているRichTextBoxに適応可能な代替ソリューションがあります。コントロールを介して-パフォーマンスに役立つはずです

2
Richard

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
Lu55

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

<jc:SelectableTextBlock Text="Some text" />
1
Robert Važan

@torvinの回答に追加し、TextTrimming="CharacterEllipsis"を有効にしている場合にコメントで@Dave Huangが言及したように、エリプシスの上にカーソルを合わせるとアプリケーションがクラッシュします。

私はTextBoxの使用についてスレッドで言及された他のオプションを試しましたが、「Ellipsis」が表示されず、テキストがコンテナのコンテンツを選択するには長すぎる場合も実際には解決策ではないようですテキストボックスは内部で「スクロール」しますが、これは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
rauland
public MainPage()
{
    this.InitializeComponent();
    ...
    ...
    ...
    //Make Start result text copiable
    TextBlockStatusStart.IsTextSelectionEnabled = true;
}
0
Angel T