Qualquer maneira de tornar um bloco de texto WPF selecionável?

Eu quero fazer o texto exibido no Witty , um cliente Twitter open source, selecionável. Atualmente é exibido usando um bloco de texto personalizado. Eu preciso usar um TextBlock porque eu estou trabalhando com inlines do textblock para exibir e formatar o @ nome de usuário e links como hiperlinks. Um pedido frequente é poder copiar e colar o texto. Para fazer isso eu preciso tornar o TextBlock selecionável.

Eu tentei fazê-lo funcionar, exibindo o texto usando um TextBox somente leitura estilo para parecer um bloco de texto, mas isso não funcionará no meu caso porque um TextBox não tem inline. Em outras palavras, eu não posso estilizar ou formatar o texto dentro de um TextBox individualmente como eu posso com um TextBlock.

Alguma ideia?

 

Todas as respostas aqui estão apenas usando um TextBox ou tentando implementar a seleção de texto manualmente, o que leva a um desempenho ruim ou a um comportamento não nativo (sinal de intercalação piscando no TextBox , sem suporte de teclado em implementações manuais etc.)

Depois de horas pesquisando e lendo o código-fonte do WPF , eu descobri uma maneira de habilitar a seleção de texto WPF nativa para controles TextBlock (ou realmente quaisquer outros controles). A maior parte da funcionalidade em torno de seleção de texto é implementada na class de sistema System.Windows.Documents.TextEditor .

Para habilitar a seleção de texto para o seu controle, você precisa fazer duas coisas:

  1. Chame TextEditor.RegisterCommandHandlers() uma vez para registrar manipuladores de events de class

  2. Crie uma instância do TextEditor para cada instância da sua class e passe a instância subjacente do seu System.Windows.Documents.ITextContainer para ela

Há também um requisito de que a propriedade Focusable do seu controle seja definida como True .

É isso! Parece fácil, mas infelizmente a class TextEditor está marcada como interna. Então eu tive que escrever uma capa de reflection sobre isso:

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

Eu também criei um SelectableTextBlock derivado de TextBlock que segue os passos indicados acima:

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

Outra opção seria criar uma propriedade anexada para o TextBlock para ativar a seleção de texto sob demanda. Neste caso, para desabilitar a seleção novamente, é necessário desappend um TextEditor usando o equivalente de reflection deste código:

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

Não consegui encontrar um exemplo de como responder à pergunta. Todas as respostas usaram uma checkbox de texto ou RichTextbox. Eu precisava de uma solução que me permitisse usar um TextBlock, e esta é a solução que criei.

Eu acredito que a maneira correta de fazer isso é estender a class TextBlock. Este é o código que eu usei para estender a class TextBlock para permitir que eu selecione o texto e copie-o para a área de transferência. “sdo” é a referência de namespace que usei no WPF.

WPF usando class estendida:

 xmlns:sdo="clr-namespace:iFaceCaseMain"  

Code Behind for Extended Class:

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

Exemplo de código da janela:

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

Crie ControlTemplate para o TextBlock e coloque um TextBox dentro com o conjunto de propriedades readonly. Ou apenas use TextBox e faça-o somente leitura, então você pode mudar o TextBox.Style para fazer com que pareça com o TextBlock.

Aplique este estilo ao seu TextBox e é isso (inspirado neste artigo ):

   

Eu não tenho certeza se você pode fazer um TextBlock selecionável, mas outra opção seria usar um RichTextBox – é como um TextBox como você sugeriu, mas suporta a formatação que você deseja.

De acordo com o Windows Dev Center :

Propriedade TextBlock.IsTextSelectionEnabled

[Atualizado para aplicativos UWP no Windows 10. Para artigos do Windows 8.x, consulte o arquivo ]

Obtém ou define um valor que indica se a seleção de texto está ativada no TextBlock , por meio da ação do usuário ou da API relacionada à seleção de chamadas.

O TextBlock não possui um modelo. Então, para conseguir isso, precisamos usar um TextBox cujo estilo é alterado para se comportar como um textBlock.

  

Há uma solução alternativa que pode ser adaptável ao RichTextBox oultined neste post de blog – ele usou um triggersdor para trocar o modelo de controle quando o uso passa sobre o controle – deve ajudar com o desempenho

Enquanto a pergunta diz “selecionável”, acredito que os resultados intencionais são para levar o texto para a área de transferência. Isto pode facilmente e elegantemente ser conseguido adicionando um Menu de Contexto e um item de menu chamado copy que coloca o valor da propriedade Textblock Text na área de transferência. Apenas uma ideia de qualquer maneira.

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

Eu implementei o SelectableTextBlock na minha biblioteca de controles de código aberto. Você pode usá-lo assim:

  
 Really nice and easy solution, exactly what I wanted ! 

Eu trago algumas pequenas modificações

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