Un moyen de rendre un bloc de texte WPF sélectionnable?

Je veux que le texte affiché dans le Witty , un client Twitter open source, puisse être sélectionné. Il est actuellement affiché en utilisant un bloc de texte personnalisé. Je dois utiliser un TextBlock car je travaille avec les lignes 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 sélectionner le TextBlock.

J’ai essayé de le faire fonctionner en affichant le texte en utilisant un TextBox en lecture seule pour ressembler à un bloc de texte, mais cela ne fonctionnera pas dans mon cas, car un TextBox n’a pas de lignes en ligne. En d’autres termes, je ne peux pas styliser ou formater le texte dans une zone de texte individuellement, comme je peux le faire avec un TextBlock.

Des idées?

Toutes les réponses ici utilisent simplement un TextBox ou essayent d’implémenter la sélection de texte manuellement, ce qui conduit à des performances médiocres ou à un comportement non natif (clignotement du curseur dans TextBox , pas de prise en charge du clavier dans les implémentations manuelles, etc.)

Après des heures de fouille et de lecture du code source WPF , j’ai 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 relatives à 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 faire deux choses:

  1. Appelez TextEditor.RegisterCommandHandlers() une fois pour enregistrer les gestionnaires d’événements de classe

  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

La propriété Focusable votre contrôle Focusable être définie sur True .

Ça y est …! Cela semble facile, mais malheureusement, la classe TextEditor est marquée comme interne. J’ai donc dû écrire un reflet autour de lui:

 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 prend les mesures indiquées 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 reflection de ce code:

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

J’ai été incapable de trouver un exemple de réponse à la question. Toutes les réponses utilisaient un Textbox ou un RichTextbox. J’avais besoin d’une solution qui me permette d’utiliser un TextBlock, et c’est la solution que j’ai créée.

Je crois 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-papier. “sdo” est la référence d’espace de noms utilisée dans WPF.

WPF utilisant la classe étendue:

 xmlns:sdo="clr-namespace:iFaceCaseMain"  

Code Behind for Extended Class:

 public partial class TextBlockMoo : TextBlock { TextPointer StartSelectPosition; TextPointer EndSelectPosition; public Ssortingng SelectedText = ""; public delegate void TextSelectedHandler(ssortingng 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 Ssortingng WindowTitle, Ssortingng ApplicationID, Ssortingng Parameters) { InitializeComponent(); /*Used to add selected text to clipboard*/ this.txtResults.TextSelected += txtResults_TextSelected; } void txtResults_TextSelected(ssortingng SelectedText) { Clipboard.SetText(SelectedText); } 

Créez ControlTemplate pour le TextBlock et placez un TextBox à l’intérieur avec le jeu de propriétés readonly. Ou utilisez simplement TextBox et rendez-le en lecture seule, puis vous pouvez changer le TextBox.Style pour qu’il ressemble à TextBlock.

Appliquez ce style à votre TextBox et c’est tout (inspiré de cet article ):

   

Je ne suis pas sûr que vous puissiez faire un TextBlock sélectionnable, mais une autre option serait d’utiliser un RichTextBox – c’est comme une TextBox comme vous l’avez suggéré, mais prend en charge le formatage souhaité.

Selon Windows Dev Center :

Propriété TextBlock.IsTextSelectionEnabled

[Mise à jour pour les applications UWP sur Windows 10. Pour les articles Windows 8.x, voir les archives ]

Obtient ou définit une valeur qui indique si la sélection de texte est activée dans TextBlock , par le biais d’une action utilisateur ou d’une API liée à une sélection d’appel.

TextBlock n’a pas de modèle. Pour ce faire, nous devons utiliser une zone de texte dont le style est modifié pour se comporter comme un bloc de texte.

  

Il existe une solution alternative qui pourrait être adaptable à RichTextBox oultined dans ce billet de blog – elle a utilisé un déclencheur pour échanger le modèle de contrôle lorsque l’utilisation survole le contrôle – devrait aider à la performance

Alors que la question dit «sélectionnable», je pense que les résultats intentionnels consistent à envoyer le texte dans le presse-papiers. Cela peut facilement et élégamment être réalisé en ajoutant un menu contextuel et un élément de menu appelé copie qui met la valeur de la propriété Text Textblock dans le presse-papiers. Juste une idée quand même.

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

  
 Really nice and easy solution, exactly what I wanted ! 

J’apporte quelques petites modifications

 public class TextBlockMoo : TextBlock { public Ssortingng SelectedText = ""; public delegate void TextSelectedHandler(ssortingng 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; } }