Qualche modo per rendere selezionabile un blocco di testo WPF?

Voglio rendere il testo visualizzato in Witty , un client Twitter open source, selezionabile. È attualmente visualizzato utilizzando un blocco di testo personalizzato. Ho bisogno di usare un TextBlock perché sto lavorando con le lineline del textblock per visualizzare e formattare @nomeutente e link come collegamenti ipertestuali. Una richiesta frequente è poter copiare e incollare il testo. Per farlo ho bisogno di rendere selezionabile TextBlock.

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

Qualche idea?

 

Tutte le risposte qui sono solo utilizzando un TextBox o cercando di implementare manualmente la selezione del testo, il che porta a prestazioni scadenti o comportamento non nativo (ammiccamento del cursore in TextBox , assenza di supporto da tastiera nelle implementazioni manuali ecc.)

Dopo ore passate a scavare e leggere il codice sorgente WPF , ho invece scoperto un modo per abilitare la selezione del testo WPF nativo per i controlli TextBlock (o per davvero altri controlli). La maggior parte delle funzionalità relative alla selezione del testo è implementata nella class 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 i gestori di eventi di class

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

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

Questo è! Sembra facile, ma sfortunatamente la class 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 prende i passaggi sopra annotati:

 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 potrebbe essere quella di creare una proprietà associata per TextBlock per abilitare la selezione del testo su richiesta. In questo caso, per disabilitare nuovamente la selezione, è necessario staccare un TextEditor usando 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 rispondere veramente alla domanda. Tutte le risposte utilizzavano una Textbox o una RichTextbox. Avevo bisogno di una soluzione che mi permettesse di usare un TextBlock e questa è la soluzione che ho creato.

Credo che il modo corretto per farlo sia estendere la class TextBlock. Questo è il codice che ho usato per estendere la class TextBlock per permettermi di selezionare il testo e copiarlo negli appunti. “sdo” è il riferimento spazio dei nomi che ho usato nel WPF.

WPF utilizzando la class estesa:

 xmlns:sdo="clr-namespace:iFaceCaseMain"  

Codice dietro per la class 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); } 

Crea ControlTemplate per TextBlock e metti un TextBox all’interno con set di proprietà readonly. Oppure usa TextBox e rendilo readonly, quindi puoi modificare TextBox.Style per renderlo simile a TextBlock.

Applica questo stile al tuo TextBox e il gioco è fatto (ispirato a questo articolo ):

  

Non sono sicuro che tu possa rendere selezionabile un TextBlock, ma un’altra opzione sarebbe utilizzare un RichTextBox: è come un TextBox come 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 su Windows 8.x, consultare l’ archivio ]

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

TextBlock non ha un modello. Quindi, al fine di raggiungere questo objective, è necessario utilizzare un controllo TextBox il cui stile viene modificato per comportarsi come un blocco testo.

  

Esiste una soluzione alternativa che potrebbe essere adattabile al RichTextBox oultined in questo post del blog : utilizzava un trigger per sostituire il modello di controllo quando l’uso si posiziona sul controllo – dovrebbe aiutare con le prestazioni

Mentre la domanda dice “Selezionabile”, credo che i risultati intenzionali siano di portare il testo negli appunti. Questo può essere facilmente ed elegantemente ottenuto aggiungendo un menu contestuale e una voce di menu chiamata copia che inserisce il valore della proprietà Textblock Text negli appunti. Comunque, solo un’idea.

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

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

  
 Really nice and easy solution, exactly what I wanted ! 

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