studio - ¿Alguna forma de hacer un bloque de texto WPF seleccionable?
visual studio wpf (14)
Aplique este estilo a su TextBox y listo (inspirado en este artículo ):
<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>
Quiero que el texto que se muestra en Witty , un cliente de código abierto de Twitter, sea seleccionable. Actualmente se muestra mediante un bloque de texto personalizado. Necesito usar un TextBlock porque estoy trabajando con las líneas en línea del bloque de texto para mostrar y formatear el nombre de usuario y los enlaces como hipervínculos. Una solicitud frecuente es poder copiar y pegar el texto. Para hacer eso necesito hacer que el TextBlock sea seleccionable.
Intenté hacer que funcionara mostrando el texto usando un TextBox de solo lectura para que pareciera un bloque de texto, pero esto no funcionará en mi caso porque un TextBox no tiene líneas de texto. En otras palabras, no puedo aplicar un estilo o formato al texto dentro de un TextBox individualmente, como puedo hacerlo con un TextBlock.
¿Algunas ideas?
Cree ControlTemplate para el TextBlock y coloque un TextBox dentro con el conjunto de propiedades de solo lectura. O simplemente use TextBox y haga que sea de solo lectura, luego puede cambiar el TextBox.Style para que se vea como TextBlock.
Existe una solución alternativa que podría ser adaptable al RichTextBox incluido en esta publicación del blog , ya que utilizó un disparador para intercambiar la plantilla de control cuando el uso se cierne sobre el control: debería ayudar con el rendimiento.
He implementado SelectableTextBlock en mi biblioteca de controles de código abierto. Puedes usarlo así:
<jc:SelectableTextBlock Text="Some text" />
No estoy seguro de poder seleccionar un TextBlock, pero otra opción sería usar un RichTextBox, es como un TextBox como sugeriste, pero admite el formato que deseas.
No he podido encontrar ningún ejemplo de cómo responder realmente la pregunta. Todas las respuestas utilizan un cuadro de texto o RichTextbox. Necesitaba una solución que me permitiera usar un TextBlock, y esta es la solución que creé.
Creo que la forma correcta de hacerlo es extender la clase TextBlock. Este es el código que utilicé para extender la clase TextBlock y permitirme seleccionar el texto y copiarlo al portapapeles. "sdo" es la referencia de espacio de nombres que usé en el WPF.
WPF usando la clase extendida:
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>
Código detrás de la clase extendida:
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);
}
}
}
Ejemplo de código de ventana:
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);
}
Según Windows Dev Center :
Propiedad TextBlock.IsTextSelectionEnabled
[Actualizado para aplicaciones UWP en Windows 10. Para artículos de Windows 8.x, vea el archive ]
Obtiene o establece un valor que indica si la selección de texto está habilitada en TextBlock , ya sea a través de la acción del usuario o llamando a la API relacionada con la selección.
Si bien la pregunta dice ''Seleccionable'', creo que los resultados intencionales son enviar el texto al portapapeles. Esto se puede lograr de manera fácil y elegante agregando un menú contextual y un elemento del menú llamado copia que coloca el valor de la propiedad Textblock Text en el portapapeles. Sólo una idea de todos modos.
TextBlock no tiene una plantilla. Así que para lograr esto, necesitamos usar un TextBox cuyo estilo se haya cambiado para que se comporte como 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>
Todas las respuestas aquí son solo usar un TextBox
o intentar implementar la selección de texto manualmente, lo que conduce a un bajo rendimiento o comportamiento no nativo (parpadeo en el TextBox
en TextBox
, no se admite el teclado en implementaciones manuales, etc.)
Después de horas de investigar y leer el código fuente de WPF , descubrí una manera de habilitar la selección nativa de texto de WPF para los controles de TextBlock
(o realmente cualquier otro control). La mayor parte de la funcionalidad en torno a la selección de texto se implementa en la clase del sistema System.Windows.Documents.TextEditor
.
Para habilitar la selección de texto para su control, necesita hacer dos cosas:
Llame a
TextEditor.RegisterCommandHandlers()
una vez para registrar los controladores de eventos de claseCree una instancia de
TextEditor
para cada instancia de su clase y pase la instancia subyacente de suSystem.Windows.Documents.ITextContainer
a ella
También hay un requisito de que la propiedad Focusable
tu control esté establecida en True
.
¡Eso es todo! Suena fácil, pero desafortunadamente la clase TextEditor
está marcada como interna. Así que tuve que escribir una envoltura de reflexión alrededor de él:
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);
}
}
También creé un SelectableTextBlock
derivado de TextBlock
que sigue los pasos mencionados anteriormente:
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);
}
}
Otra opción sería crear una propiedad adjunta para TextBlock
para habilitar la selección de texto a pedido. En este caso, para volver a deshabilitar la selección, es necesario desconectar un TextEditor
utilizando el reflejo equivalente de este código:
_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;
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
}
};
Really nice and easy solution, exactly what I wanted !
Traigo algunas pequeñas modificaciones.
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;
}
}
public MainPage()
{
this.InitializeComponent();
...
...
...
//Make Start result text copiable
TextBlockStatusStart.IsTextSelectionEnabled = true;
}
<TextBox Background="Transparent"
BorderThickness="0"
Text="{Binding Text, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap" />