Question

Je souhaite que le texte affiché dans le Witty , un client Twitter à code source ouvert, sélectionnable. Il est actuellement affiché en utilisant un bloc de texte personnalisé. Je dois utiliser un TextBlock car je travaille avec les inlines du bloc de texte pour afficher et formater le @ nom d'utilisateur et les liens en tant que liens hypertexte. Une demande fréquente est de pouvoir copier-coller le texte. Pour ce faire, je dois rendre le TextBlock sélectionnable.

J'ai essayé de le faire fonctionner en affichant le texte à l'aide d'une zone de texte en lecture seule de style ressemblant à un bloc de texte, mais cela ne fonctionnera pas dans mon cas car une zone de texte ne contient pas de texte en ligne. En d'autres termes, je ne peux pas styler ou mettre en forme le texte dans une zone de texte individuellement, contrairement à un TextBlock.

Des idées?

Était-ce utile?

La solution

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

Autres conseils

Toutes les réponses ici ne font qu'utiliser une TextBox ou essayer d'implémenter la sélection de texte manuellement, ce qui entraîne des performances médiocres ou un comportement non natif (curseur clignotant dans TextBox , pas de prise en charge du clavier dans les implémentations manuelles, etc.)

Après des heures consacrées à la lecture et à la lecture de Code source WPF , j’ai plutôt découvert un moyen d’activer la sélection de texte WPF native pour les contrôles TextBlock (ou tout autre contrôle). La plupart des fonctionnalités entourant la sélection de texte sont implémentées dans la classe système System.Windows.Documents.TextEditor .

Pour activer la sélection de texte pour votre contrôle, vous devez effectuer deux opérations:

  1. Appelez TextEditor.RegisterCommandHandlers () une fois pour enregistrer la classe. gestionnaires d'événements

  2. Créez une instance de TextEditor pour chaque instance de votre classe et transmettez-lui l'instance sous-jacente de votre System.Windows.Documents.ITextContainer

Vous devez également définir la propriété Focusable de votre contrôle sur True .

C'est ça! Cela semble facile, mais malheureusement, la classe TextEditor est marquée comme interne. Je devais donc écrire une feuille de réflexion autour de celle-ci:

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

J'ai également créé un SelectableTextBlock dérivé de TextBlock qui procède comme indiqué ci-dessus:

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

Une autre option serait de créer une propriété attachée pour TextBlock afin d'activer la sélection de texte à la demande. Dans ce cas, pour désactiver à nouveau la sélection, il faut détacher un TextEditor en utilisant l'équivalent de réflexion de ce code:

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

Je suis incapable de trouver un exemple de réponse réelle à la question. Toutes les réponses utilisaient une zone de texte ou une zone de texte. J'avais besoin d'une solution qui me permettait d'utiliser un TextBlock, et c'est la solution que j'ai créée.

Je pense que la bonne façon de faire est d’étendre la classe TextBlock. C'est le code que j'ai utilisé pour étendre la classe TextBlock afin de me permettre de sélectionner le texte et de le copier dans le Presse-papiers. " sdo " est la référence de l'espace de noms que j'ai utilisée dans WPF.

WPF utilisant la classe étendue:

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 derrière pour la classe étendue:

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

Exemple de code de fenêtre:

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

Appliquez ce style à votre zone de texte et le tour est joué (inspiré de cet 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>

Créez ControlTemplate pour TextBlock et placez une zone de texte à l'intérieur avec le jeu de propriétés en lecture seule. Ou utilisez simplement TextBox et faites-le en lecture seule, vous pourrez alors changer le TextBox.Style pour qu’il ressemble à TextBlock.

Je ne sais pas si vous pouvez sélectionner un TextBlock, mais une autre option consisterait à utiliser un RichTextBox. Il ressemble à un TextBox comme vous l'avez suggéré, mais prend en charge le formatage souhaité.

Selon Centre de développement Windows :

  

Propriété TextBlock.IsTextSelectionEnabled

     

[Mise à jour pour les applications UWP sous Windows 10. Pour des articles sur Windows 8.x, voir   la archive ]

     

Obtient ou définit une valeur qui indique si la sélection de texte est activée   dans le TextBlock , via l'action de l'utilisateur ou en appelant   API liée à la sélection.

TextBlock n'a pas de modèle. Donc, pour cela, nous devons utiliser une zone de texte dont le style a été modifié pour se comporter comme un 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>

Bien que la question dise "Sélectionnable", je pense que le résultat intentionnel est de placer le texte dans le presse-papiers. Ceci peut être facilement et élégamment réalisé en ajoutant un menu contextuel et un élément de menu appelé copie qui place la valeur de la propriété Textblock dans le presse-papiers. Juste une idée quand même.

Il existe une solution alternative pouvant être adaptée à la RichTextBox indiquée dans cette article de blog - il a utilisé un déclencheur pour échanger le modèle de contrôle lorsque l'utilisation survole le contrôle - devrait contribuer à la performance


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

J'ai implémenté SelectableTextBlock dans ma bibliothèque de contrôles opensource . Vous pouvez l'utiliser comme ceci:

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

Ajout à la réponse de @ torvin et comme mentionné par @Dave Huang dans les commentaires si vous avez TextTrimming = "CharacterEllipsis" activé, l'application se bloque lorsque vous survolez les points de suspension.

J'ai essayé d'autres options mentionnées dans le fil de discussion sur l'utilisation d'une zone de texte, mais cela ne semble vraiment pas être la solution non plus, car il ne montre pas les ellipses et si le texte est trop long pour s'adapter au conteneur, sélectionnez le contenu de la zone de texte 'scrolls' en interne qui n'est pas un comportement TextBlock.

Je pense que la meilleure solution est la réponse de @ torvin, mais a le vilain accident en survolant les points de suspension.

Je sais que ce n’est pas beau, mais s’abonner / désabonner en interne à des exceptions non gérées et le traitement de l’exception était le seul moyen que j’ai trouvé pour résoudre ce problème. Veuillez le partager si quelqu'un a une meilleure 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;
            }
        }
    }
}
Really nice and easy solution, exactly what I wanted !

J'apporte quelques petites 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;
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top