Barra de desplazamiento vertical automática en WPF TextBlock?

336

Tengo un TextBlocken WPF. Le escribo muchas líneas, superando con creces su altura vertical. Esperaba que una barra de desplazamiento vertical apareciera automáticamente cuando eso sucede, pero no fue así. Traté de buscar una propiedad de barra de desplazamiento en el panel Propiedades, pero no pude encontrar una.

¿Cómo puedo hacer que la barra de desplazamiento vertical se cree automáticamente para mi TextBlockuna vez que su contenido exceda su altura?

Aclaración: prefiero hacerlo desde el diseñador y no escribiendo directamente a XAML.

Bab Yogoo
fuente
1
Al volver a leer esta pregunta, noto que mencionas TextBlockdos veces y TextBoxuna vez.
Drew Noakes

Respuestas:

555

Envuélvelo en un visor de desplazamiento:

<ScrollViewer>
    <TextBlock />
</ScrollViewer>

NOTA: esta respuesta se aplica a un TextBlock(un elemento de texto de solo lectura) como se solicitó en la pregunta original.

Si desea mostrar barras de desplazamiento en un TextBox(un elemento de texto editable), use las ScrollViewerpropiedades adjuntas:

<TextBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
         ScrollViewer.VerticalScrollBarVisibility="Auto" />

Los valores válidos para estas dos propiedades son Disabled, Auto, Hiddeny Visible.

Drew Noakes
fuente
2
¿Cómo lo hago desde el diseñador?
Bab Yogoo
16
Lo siento, no estoy seguro, no uso el diseñador WPF. Creo que si agrega XAML directamente, el diseñador se actualizará solo.
Drew Noakes
55
@conqenator TextBox.ScrollToEnd ();
Petey B
2
@ Greg, la pregunta es sobre TextBlockno TextBox.
Drew Noakes
77
A veces se necesita un MaxHeight en el Scrollviewer para forzar la aparición del rollo si el elemento que lo encierra no impone ninguna altura.
HackerBaloo
106

puede usar lo siguiente ahora:

<TextBox Name="myTextBox" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True">SOME TEXT
</TextBox>
Vince
fuente
19
@jjnguy, interpreté la pregunta original como TextBlockno acerca de TextBox(como en el título y la línea de apertura), pero el segundo párrafo mencionado TextBox. Para ser claros, esta respuesta es definitivamente el mejor enfoque para los cuadros de texto , y el mío es el mejor que conozco para los bloques de texto :)
Drew Noakes
@Drew, ah, tiene sentido. Gracias por la aclaración.
jjnguy
2
Funcionó mejor para mí también. Al menos para un TextBox, cuando se usa el ScrollViewer a su alrededor, como en la respuesta aceptada, los bordes del TextBox desaparecen, porque todo el control se desplaza, y no solo su contenido.
Fueled
20

Algo mejor sería:

<Grid Width="Your-specified-value" >
    <ScrollViewer>
         <TextBlock Width="Auto" TextWrapping="Wrap" />
    </ScrollViewer>
</Grid>

Esto asegura que el texto en su bloque de texto no se desborde y se superponga a los elementos debajo del bloque de texto, como puede ser el caso si no utiliza la cuadrícula. Eso me sucedió cuando probé otras soluciones a pesar de que el bloque de texto ya estaba en una cuadrícula con otros elementos. Tenga en cuenta que el ancho del bloque de texto debe ser Automático y debe especificar el deseado en el elemento Cuadrícula. Hice esto en mi código y funciona muy bien. HTH

varagrawal
fuente
7
<ScrollViewer Height="239" VerticalScrollBarVisibility="Auto">
    <TextBox AcceptsReturn="True" TextWrapping="Wrap" LineHeight="10" />
</ScrollViewer>

Esta es la forma de usar TextBox de desplazamiento en XAML y usarlo como un área de texto.

Juan
fuente
1
La pregunta está relacionada con TextBlockno TextBox.
Afzaal Ahmad Zeeshan
No es la respuesta correcta, pero descubrí que VerticalScrollBarVisibility es una pista útil, así que +1
Malaquías el
4

Esta respuesta describe una solución usando MVVM.

Esta solución es excelente si desea agregar un cuadro de registro a una ventana, que se desplaza automáticamente hacia abajo cada vez que se agrega un nuevo mensaje de registro.

Una vez que se agregan estas propiedades adjuntas, se pueden reutilizar en cualquier lugar, por lo que se convierte en un software muy modular y reutilizable.

Agregue este XAML:

<TextBox IsReadOnly="True"   
         Foreground="Gainsboro"                           
         FontSize="13" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True"
         attachedBehaviors:TextBoxApppendBehaviors.AppendText="{Binding LogBoxViewModel.AttachedPropertyAppend}"                                       
         attachedBehaviors:TextBoxClearBehavior.TextBoxClear="{Binding LogBoxViewModel.AttachedPropertyClear}"                                    
         TextWrapping="Wrap">

Agregue esta propiedad adjunta:

public static class TextBoxApppendBehaviors
{
    #region AppendText Attached Property
    public static readonly DependencyProperty AppendTextProperty =
        DependencyProperty.RegisterAttached(
            "AppendText",
            typeof (string),
            typeof (TextBoxApppendBehaviors),
            new UIPropertyMetadata(null, OnAppendTextChanged));

    public static string GetAppendText(TextBox textBox)
    {
        return (string)textBox.GetValue(AppendTextProperty);
    }

    public static void SetAppendText(
        TextBox textBox,
        string value)
    {
        textBox.SetValue(AppendTextProperty, value);
    }

    private static void OnAppendTextChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue == null)
        {
            return;
        }

        string toAppend = args.NewValue.ToString();

        if (toAppend == "")
        {
            return;
        }

        TextBox textBox = d as TextBox;
        textBox?.AppendText(toAppend);
        textBox?.ScrollToEnd();
    }
    #endregion
}

Y esta propiedad adjunta (para borrar el cuadro):

public static class TextBoxClearBehavior
{
    public static readonly DependencyProperty TextBoxClearProperty =
        DependencyProperty.RegisterAttached(
            "TextBoxClear",
            typeof(bool),
            typeof(TextBoxClearBehavior),
            new UIPropertyMetadata(false, OnTextBoxClearPropertyChanged));

    public static bool GetTextBoxClear(DependencyObject obj)
    {
        return (bool)obj.GetValue(TextBoxClearProperty);
    }

    public static void SetTextBoxClear(DependencyObject obj, bool value)
    {
        obj.SetValue(TextBoxClearProperty, value);
    }

    private static void OnTextBoxClearPropertyChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs args)
    {
        if ((bool)args.NewValue == false)
        {
            return;
        }

        var textBox = (TextBox)d;
        textBox?.Clear();
    }
}   

Luego, si está utilizando un marco de inyección de dependencias como MEF, puede colocar todo el código específico de registro en su propio ViewModel:

public interface ILogBoxViewModel
{
    void CmdAppend(string toAppend);
    void CmdClear();

    bool AttachedPropertyClear { get; set; }

    string AttachedPropertyAppend { get; set; }
}

[Export(typeof(ILogBoxViewModel))]
public class LogBoxViewModel : ILogBoxViewModel, INotifyPropertyChanged
{
    private readonly ILog _log = LogManager.GetLogger<LogBoxViewModel>();

    private bool _attachedPropertyClear;
    private string _attachedPropertyAppend;

    public void CmdAppend(string toAppend)
    {
        string toLog = $"{DateTime.Now:HH:mm:ss} - {toAppend}\n";

        // Attached properties only fire on a change. This means it will still work if we publish the same message twice.
        AttachedPropertyAppend = "";
        AttachedPropertyAppend = toLog;

        _log.Info($"Appended to log box: {toAppend}.");
    }

    public void CmdClear()
    {
        AttachedPropertyClear = false;
        AttachedPropertyClear = true;

        _log.Info($"Cleared the GUI log box.");
    }

    public bool AttachedPropertyClear
    {
        get { return _attachedPropertyClear; }
        set { _attachedPropertyClear = value; OnPropertyChanged(); }
    }

    public string AttachedPropertyAppend
    {
        get { return _attachedPropertyAppend; }
        set { _attachedPropertyAppend = value; OnPropertyChanged(); }
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

Así es como funciona:

  • ViewModel alterna las Propiedades adjuntas para controlar TextBox.
  • Como está usando "Append", es muy rápido.
  • Cualquier otro ViewModel puede generar mensajes de registro llamando a métodos en el ViewModel de registro.
  • A medida que utilizamos el ScrollViewer integrado en TextBox, podemos hacer que se desplace automáticamente hasta la parte inferior del cuadro de texto cada vez que se agrega un nuevo mensaje.
Aplazamiento de pago
fuente
4
<ScrollViewer MaxHeight="50"  
              Width="Auto" 
              HorizontalScrollBarVisibility="Disabled"
              VerticalScrollBarVisibility="Auto">
     <TextBlock Text="{Binding Path=}" 
                Style="{StaticResource TextStyle_Data}" 
                TextWrapping="Wrap" />
</ScrollViewer>

Estoy haciendo esto de otra manera al poner MaxHeight en ScrollViewer.

Simplemente ajuste la altura máxima para mostrar más o menos líneas de texto. Fácil.

Tony Wu
fuente
1

Traté de hacer que estas sugerencias funcionen para un bloque de texto, pero no pude hacerlo funcionar. Incluso intenté que el diseñador lo hiciera funcionar. (Mire en Diseño y expanda la lista haciendo clic en la flecha hacia abajo "V" en la parte inferior) Intenté configurar el visor de desplazamiento en Visible y luego en Automático , pero aún así no funcionaría.

Finalmente me di por vencido y cambié TextBlocka a TextBoxcon el conjunto de atributos Readonly , y funcionó de maravilla .

Scott Bordelon
fuente
0

No sé si alguien más tiene este problema, pero envolverme TextBlocken una ScrollViewerinterfaz de usuario de alguna manera estropeó mi interfaz de usuario; como una simple solución, descubrí que reemplazarlo TextBlockpor uno TextBoxcomo este

<TextBox  Name="textBlock" SelectionBrush="Transparent" Cursor="Arrow" IsReadOnly="True" Text="My Text" VerticalScrollBarVisibility="Auto">

crea un TextBoxque se ve y se comporta como un TextBlockcon una barra de desplazamiento (y puede hacerlo todo en el diseñador).

dunkleosteus
fuente
0

Esta es una solución simple a esa pregunta. El desplazamiento vertical se activará solo cuando el texto se desborde.

<TextBox Text="Try typing some text here " ScrollViewer.VerticalScrollBarVisibility="Auto" TextWrapping="WrapWithOverflow" />

Zuhair
fuente