Pregunta

Quiero hacer que el texto se muestre en Witty , un cliente de código abierto de Twitter, 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.

Traté de hacer que funcionara al mostrar 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 inlines. En otras palabras, no puedo aplicar un estilo o formato al texto dentro de un TextBox individualmente como puedo hacerlo con un TextBlock.

¿Alguna idea?

¿Fue útil?

Solución

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

Otros consejos

Todas las respuestas aquí solo utilizan un TextBox o intentan implementar la selección de texto manualmente, lo que conduce a un rendimiento deficiente o un comportamiento no nativo (parpadeo en el símbolo de TextBox , no hay soporte de teclado en implementaciones manuales, etc.)

Después de horas de investigar y leer Código fuente de WPF , en cambio, descubrí una forma de habilitar la selección de texto nativa de WPF para los controles TextBlock (o en realidad cualquier otro control). La mayor parte de la funcionalidad en torno a la selección de texto se implementa en la clase de sistema System.Windows.Documents.TextEditor .

Para habilitar la selección de texto para su control, debe hacer dos cosas:

  1. Llame a TextEditor.RegisterCommandHandlers () una vez para registrar la clase manejadores de eventos

  2. Cree una instancia de TextEditor para cada instancia de su clase y pase la instancia subyacente de su System.Windows.Documents.ITextContainer a ella

También existe el requisito de que la propiedad Focusable de tu control esté establecida en True .

Esto es todo! Suena fácil, pero desafortunadamente la clase TextEditor está marcada como interna. Así que tuve que escribir un envoltorio de reflexión a su alrededor:

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 toma 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 inhabilitar la selección, es necesario separar un TextEditor utilizando el reflejo equivalente de este código:

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

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 utilicé en el WPF.

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

Código de ventana de ejemplo:

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

Aplique este estilo a su TextBox y listo. eshaham / archive / 2009/07/06 / selectable-text-control.aspx "rel =" nofollow noreferrer "> 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>

Cree ControlTemplate para 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 TextBox.Style para que se vea como TextBlock.

No estoy seguro de poder seleccionar un TextBlock, pero otra opción sería utilizar un RichTextBox, es como un TextBox como sugeriste, pero admite el formato que deseas.

Según Centro de desarrollo de Windows :

  

Propiedad TextBlock.IsTextSelectionEnabled

     

[Actualizado para las aplicaciones UWP en Windows 10. Para artículos de Windows 8.x, vea   el archivo ]

     

Obtiene o establece un valor que indica si la selección de texto está habilitada   en el TextBlock , ya sea a través de la acción del usuario o llamando   API relacionada con la selección.

TextBlock no tiene una plantilla. Por lo tanto, 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>

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 al agregar un menú contextual y un elemento del menú llamado copia que coloca el valor de la propiedad Textblock Text en el portapapeles. Solo una idea de todos modos.

Hay una solución alternativa que podría ser adaptable al RichTextBox incluido en este blog post : utiliza un disparador para intercambiar la plantilla de control cuando el uso se desplaza sobre el control. Debería ayudar con el rendimiento


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

He implementado SelectableTextBlock en mi biblioteca de controles de código abierto . Puedes usarlo así:

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

Agregando a la respuesta de @ torvin y como @Dave Huang se menciona en los comentarios si tienes TextTrimming = " CharacterEllipsis " habilitó la aplicación cuando se desplaza sobre los puntos suspensivos.

Probé otras opciones mencionadas en el hilo sobre el uso de un TextBox, pero realmente no parece ser la solución, ya que no muestra los 'puntos suspensivos' y también si el texto es demasiado largo para ajustarse al contenedor seleccionado. el contenido del cuadro de texto se desplaza internamente, lo que no es un comportamiento de TextBlock.

Creo que la mejor solución es la respuesta de @ torvin, pero tiene la desagradable caída al pasar el cursor sobre los puntos suspensivos.

Sé que no es bonito, pero la única forma que encontré de resolver este problema fue suscribirse / cancelar la suscripción a excepciones no manejadas y manejar la excepción,

comparta si alguien tiene una solución mejor :)

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 !

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;
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top