Domanda

Voglio rendere il testo visualizzato nel Witty , un client Twitter open source, selezionabile. Attualmente è visualizzato utilizzando un blocco di testo personalizzato. Devo usare un TextBlock perché sto lavorando con gli inline del textblock per visualizzare e formattare @nomeutente e collegamenti come collegamenti ipertestuali. Una richiesta frequente è quella di poter copiare e incollare il testo. Per fare ciò devo rendere selezionabile TextBlock.

Ho provato a farlo funzionare visualizzando il testo usando un TextBox di sola lettura in stile simile a un blocco di testo, ma questo non funzionerà nel mio caso perché un TextBox non ha inline. In altre parole, non posso modellare o formattare il testo all'interno di una TextBox singolarmente come posso fare con un TextBlock.

Qualche idea?

È stato utile?

Soluzione

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

Altri suggerimenti

Tutte le risposte qui stanno semplicemente usando un TextBox o stanno provando a implementare manualmente la selezione del testo, il che porta a scarse prestazioni o comportamenti non nativi (lampeggio del cursore in TextBox , nessun supporto tastiera nelle implementazioni manuali ecc.)

Dopo ore di scavare e leggere Codice sorgente WPF , ho invece scoperto un modo per abilitare la selezione del testo WPF nativo per i controlli TextBlock (o qualsiasi altro controllo). La maggior parte delle funzionalità relative alla selezione del testo è implementata nella classe di sistema System.Windows.Documents.TextEditor .

Per abilitare la selezione del testo per il tuo controllo devi fare due cose:

  1. Chiama TextEditor.RegisterCommandHandlers () una volta per registrare la classe gestori di eventi

  2. Crea un'istanza di TextEditor per ogni istanza della tua classe e passa l'istanza sottostante del tuo System.Windows.Documents.ITextContainer ad essa

È inoltre richiesto che la proprietà Focusable del tuo controllo sia impostata su True .

Questo è tutto! Sembra facile, ma sfortunatamente la classe TextEditor è contrassegnata come interna. Quindi ho dovuto scrivere un involucro di riflessione attorno ad esso:

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

Ho anche creato un SelectableTextBlock derivato da TextBlock che esegue i passaggi sopra indicati:

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

Un'altra opzione sarebbe quella di creare una proprietà collegata per TextBlock per abilitare la selezione del testo su richiesta. In questo caso, per disabilitare nuovamente la selezione, è necessario scollegare un TextEditor utilizzando l'equivalente di riflessione di questo codice:

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

Non sono stato in grado di trovare alcun esempio di come rispondere realmente alla domanda. Tutte le risposte hanno utilizzato una casella di testo o RichTextbox. Avevo bisogno di una soluzione che mi permettesse di utilizzare un TextBlock e questa è la soluzione che ho creato.

Credo che il modo corretto per farlo sia estendere la classe TextBlock. Questo è il codice che ho usato per estendere la classe TextBlock per permettermi di selezionare il testo e copiarlo negli appunti. & Quot; SDO " è il riferimento allo spazio dei nomi che ho usato nel WPF.

WPF utilizzando la classe estesa:

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>

Codice disponibile per la classe estesa:

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

Esempio di codice finestra:

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

Applica questo stile al tuo TextBox e basta (ispirato a questo articolo ):

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

Crea ControlTemplate per TextBlock e inserisci una TextBox all'interno con il set di proprietà readonly. O semplicemente usa TextBox e rendilo di sola lettura, quindi puoi modificare TextBox.Style per renderlo simile a TextBlock.

Non sono sicuro di poter rendere selezionabile un TextBlock, ma un'altra opzione sarebbe quella di utilizzare un RichTextBox: è come un TextBox come hai suggerito, ma supporta la formattazione che desideri.

Secondo Windows Dev Center :

  

Proprietà TextBlock.IsTextSelectionEnabled

     

[Aggiornato per le app UWP su Windows 10. Per gli articoli di Windows 8.x, vedere   l ' archivio ]

     

Ottiene o imposta un valore che indica se la selezione del testo è abilitata   nel TextBlock , sia tramite l'azione dell'utente o chiamando   API relativa alla selezione.

TextBlock non ha un modello. Quindi, per raggiungere questo obiettivo, dobbiamo usare una TextBox il cui stile è cambiato per comportarsi come 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>

Mentre la domanda dice "Selezionabile", credo che i risultati intenzionali siano di portare il testo negli appunti. Ciò può essere ottenuto facilmente ed elegantemente aggiungendo un menu contestuale e una voce di menu chiamata copia che inserisce il valore della proprietà Text TextBlock negli Appunti. Solo un'idea comunque.

Esiste una soluzione alternativa che potrebbe essere adattabile a RichTextBox oultined in questo post di blog - ha usato un trigger per scambiare il modello di controllo quando l'uso passa sopra il controllo - dovrebbe aiutare con le prestazioni


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

Ho implementato SelectableTextBlock nella mia libreria di controlli opensource . Puoi usarlo in questo modo:

<jc:SelectableTextBlock Text="Some text" />
public MainPage()
{
    this.InitializeComponent();
    ...
    ...
    ...
    //Make Start result text copiable
    TextBlockStatusStart.IsTextSelectionEnabled = true;
}

Aggiungendo alla risposta di @ torvin e come indicato da @Dave Huang nei commenti se hai TextTrimming = " CharacterEllipsis " hai abilitato l'arresto anomalo dell'applicazione quando passi con il mouse sopra i puntini di sospensione.

Ho provato altre opzioni menzionate nel thread sull'utilizzo di un TextBox ma in realtà non sembra essere la soluzione, in quanto non mostra i "puntini di sospensione" e anche se il testo è troppo lungo per adattarsi al contenitore selezionando il contenuto della casella di testo "scorre" internamente, il che non è un comportamento TextBlock.

Penso che la soluzione migliore sia la risposta di @ torvin, ma ha il brutto incidente quando si passa sopra i puntini di sospensione.

So che non è carino, ma iscriversi / annullare l'iscrizione internamente a eccezioni non gestite e gestire l'eccezione è stato l'unico modo che ho trovato per risolvere questo problema, per favore condividi se qualcuno ha una soluzione migliore :)

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;
            }
        }
    }
}
Really nice and easy solution, exactly what I wanted !

Apporto alcune piccole modifiche

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;
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top