it-swarm.com.de

Irgendeine Möglichkeit, einen WPF-Textblock auswählbar zu machen?

Ich möchte, dass der Text im Witty , einem Open-Source-Twitter-Client, auswählbar ist. Es wird derzeit mit einem benutzerdefinierten Textblock angezeigt. Ich muss einen TextBlock verwenden, da ich mit den Inlines des Textblocks arbeite, um den @Benutzernamen und die Hyperlinks als Hyperlinks anzuzeigen und zu formatieren. Eine häufige Anfrage ist, den Text kopieren und einfügen zu können. Dazu muss ich den TextBlock auswählbar machen.

Ich habe versucht, es zum Laufen zu bringen, indem ich den Text mit einem schreibgeschützten TextBox-Display anzeigte, das wie ein Textblock aussieht. Dies funktioniert jedoch in meinem Fall nicht, da ein TextBox-Objekt keine Inlines enthält. Mit anderen Worten, ich kann den Text in einer TextBox nicht individuell gestalten oder formatieren, wie dies bei einem TextBlock der Fall ist.

Irgendwelche Ideen?

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

Alle Antworten hier verwenden nur eine TextBox oder versuchen, die Textauswahl manuell zu implementieren, was zu einer schlechten Leistung oder zu nicht-nativem Verhalten führt (blinkendes Caret in TextBox, keine Tastaturunterstützung bei manuellen Implementierungen usw.)

Nach stundenlangem Graben und Lesen des WPF-Quellcodes entdeckte ich stattdessen eine Möglichkeit, die native WPF-Textauswahl für TextBlock-Steuerelemente (oder wirklich andere Steuerelemente) zu aktivieren. Die meisten Funktionen für die Textauswahl sind in der Systemklasse System.Windows.Documents.TextEditor implementiert. 

Um die Textauswahl für Ihr Steuerelement zu aktivieren, müssen Sie zwei Schritte ausführen:

  1. Rufen Sie einmal TextEditor.RegisterCommandHandlers() auf, um die Ereignisbehandlungsroutinen der Klasse Zu registrieren

  2. Erstellen Sie eine Instanz von TextEditor für jede Instanz Ihrer Klasse, und übergeben Sie die zugrunde liegende Instanz Ihres System.Windows.Documents.ITextContainer an diese

Es ist auch eine Anforderung, dass die Focusable-Eigenschaft Ihres Steuerelements auf True gesetzt ist.

Das ist es! Klingt einfach, aber leider wird TextEditor als intern markiert. Also musste ich einen Reflection-Wrapper darum schreiben:

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

Ich habe auch eine SelectableTextBlock erstellt, die von TextBlock abgeleitet ist und die oben genannten Schritte ausführt:

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

Eine weitere Option wäre das Erstellen einer angefügten Eigenschaft für TextBlock, um die Textauswahl auf Anforderung zu aktivieren. Um die Auswahl wieder zu deaktivieren, müssen Sie in diesem Fall eine TextEditor trennen, indem Sie das entsprechende Reflection-Äquivalent dieses Codes verwenden:

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

Ich konnte kein Beispiel finden, um die Frage wirklich zu beantworten. Alle Antworten verwendeten ein Textfeld oder ein RichTextbox. Ich brauchte eine Lösung, die es mir erlaubte, einen TextBlock zu verwenden, und dies ist die von mir erstellte Lösung. 

Ich glaube, dass der korrekte Weg, dies zu tun, darin besteht, die TextBlock-Klasse zu erweitern. Dies ist der Code, mit dem ich die TextBlock-Klasse erweitert habe, um den Text auswählen und in die Zwischenablage kopieren zu können. "sdo" ist die Namespace-Referenz, die ich in der WPF verwendet habe. 

WPF mit erweiterter Klasse:

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 hinter der erweiterten Klasse:

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

Beispielfenstercode:

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

Erstellen Sie ControlTemplate für den TextBlock und fügen Sie eine TextBox mit readonly-Eigenschaftssatz ein .. Oder verwenden Sie einfach TextBox und machen Sie Readonly, und Sie können TextBox.Style so ändern, dass es wie TextBlock aussieht.

19
Jobi Joy

Wenden Sie diesen Stil auf Ihre TextBox an und das wars (von diesem Artikel

<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>
19
juanjo.arana

Gemäß Windows Dev Center :

TextBlock.IsTextSelectionEnabled-Eigenschaft

[Für UWP-Apps unter Windows 10 aktualisiert. Artikel zu Windows 8.x finden Sie unter das Archiv ]

Ruft einen Wert ab oder legt einen Wert fest, der angibt, ob die Textauswahl aktiviert ist in TextBlock entweder durch Benutzeraktion oder durch Aufrufen von Auswahl-bezogene API.

9
Jack Pines

Ich bin nicht sicher, ob Sie einen TextBlock auswählbar machen können, aber eine andere Option wäre die Verwendung einer RichTextBox - sie ist wie eine TextBox, wie Sie es vorgeschlagen haben, unterstützt jedoch die gewünschte Formatierung.

9
Bruce

TextBlock hat keine Vorlage. Um dies zu erreichen, müssen wir eine TextBox verwenden, deren Stil geändert wird, um sich als TextBlock zu verhalten. 

<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

Während die Frage 'Selectable' sagt, glaube ich, dass das absichtliche Ergebnis darin besteht, den Text in die Zwischenablage zu bringen. Dies kann einfach und elegant durch Hinzufügen eines Kontextmenüs und eines Menüelements namens "copy" erreicht werden, das den Textblock-Text-Eigenschaftswert in die Zwischenablage setzt. Nur eine Idee.

2
SimperT

Es gibt eine alternative Lösung, die an die RichTextBox angepasst werden kann, die in diesem Blogbeitrag enthalten ist - sie hat einen Auslöser zum Austauschen der Steuerelementvorlage verwendet, wenn die Verwendung über dem Steuerelement führt - die Leistung verbessern sollte

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

Ich habe SelectableTextBlock in meiner OpenSource-Steuerelementbibliothek implementiert. Sie können es so verwenden:

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

Wenn Sie zu @ torvins Antwort hinzufügen und @ Dave Huang in den Kommentaren erwähnen, dass Sie TextTrimming="CharacterEllipsis" aktiviert haben, stürzt die Anwendung ab, wenn Sie mit der Maus über die Ellipse fahren.

Ich habe andere im Thread erwähnte Optionen zur Verwendung einer TextBox ausprobiert, aber es scheint auch nicht die Lösung zu sein, da die 'Ellipse' nicht angezeigt wird und der Text zu lang ist, um in den Container zu passen, in dem der Inhalt ausgewählt wird Das Textfeld 'scrollt' intern, was kein TextBlock-Verhalten ist.

Ich denke, die beste Lösung ist @ torvins Antwort, aber der Absturz beim Schweben über der Ellipse ist fies.

Ich weiß, dass es nicht schön ist, aber das interne Abonnieren/Abbestellen von nicht behandelten Ausnahmen und das Behandeln der Ausnahme war die einzige Möglichkeit, die ich zur Lösung dieses Problems gefunden habe. Teilen Sie uns bitte mit, wenn jemand eine bessere Lösung hat :)

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