Barre de défilement verticale automatique dans WPF TextBlock?

336

J'ai un TextBlockdans WPF. Je lui écris de nombreuses lignes, dépassant de loin sa hauteur verticale. Je m'attendais à ce qu'une barre de défilement verticale apparaisse automatiquement lorsque cela se produit, mais ce n'est pas le cas. J'ai essayé de rechercher une propriété de barre de défilement dans le volet Propriétés, mais je n'en ai pas trouvé.

Comment puis-je créer une barre de défilement verticale créée automatiquement pour mon TextBlockune fois que son contenu dépasse sa hauteur?

Précision: je préfère le faire par le concepteur et non en écrivant directement sur le XAML.

Bab Yogoo
la source
1
En relisant cette question, je remarque que vous mentionnez TextBlockdeux fois et TextBoxune fois.
Drew Noakes

Réponses:

555

Enveloppez-le dans une visionneuse à défilement:

<ScrollViewer>
    <TextBlock />
</ScrollViewer>

REMARQUE cette réponse s'applique à un TextBlock(un élément de texte en lecture seule) comme demandé dans la question d'origine.

Si vous souhaitez afficher les barres de défilement dans un TextBox(un élément de texte modifiable), utilisez les ScrollViewerpropriétés jointes:

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

Les valeurs valides pour ces deux propriétés sont Disabled, Auto, Hiddenet Visible.

Drew Noakes
la source
2
Comment puis-je le faire à partir du concepteur?
Bab Yogoo le
16
Désolé, je ne suis pas sûr, je n'utilise pas le concepteur WPF. Je pense que si vous ajoutez directement le XAML, le concepteur se mettra à jour.
Drew Noakes
5
@conqenator TextBox.ScrollToEnd ();
Petey B
2
@Greg, la question n'est TextBlockpas TextBox.
Drew Noakes
7
Parfois, un MaxHeight sur le Scrollviewer est nécessaire pour forcer le défilement à apparaître si l'élément englobant n'applique aucune hauteur.
HackerBaloo
106

peut utiliser les éléments suivants maintenant:

<TextBox Name="myTextBox" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True">SOME TEXT
</TextBox>
vince
la source
19
@jjnguy, j'ai interprété la question d'origine comme ne concernant TextBlockpas TextBox(comme dans le titre et la première ligne), mais le deuxième paragraphe mentionné TextBox. Pour être clair, cette réponse est certainement la meilleure approche pour les zones de texte , et la mienne est la meilleure que je connaisse pour les blocs de texte :)
Drew Noakes
@Drew, ah, c'est logique. Merci pour la clarification.
jjnguy
2
Ça a mieux marché pour moi aussi. Pour un TextBox au moins, lorsque vous utilisez le ScrollViewer autour de lui, comme dans la réponse acceptée, les bordures du TextBox disparaissent, car tout le contrôle est défilé, et pas seulement son contenu.
Alimenté le
20

Quelque chose de mieux serait:

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

Cela garantit que le texte de votre bloc de texte ne déborde pas et ne chevauche pas les éléments situés sous le bloc de texte, comme cela peut être le cas si vous n'utilisez pas la grille. Cela m'est arrivé lorsque j'ai essayé d'autres solutions même si le bloc de texte était déjà dans une grille avec d'autres éléments. Gardez à l'esprit que la largeur du bloc de texte doit être Auto et vous devez spécifier le souhaité avec dans l'élément Grille. Je l'ai fait dans mon code et cela fonctionne à merveille. HTH.

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

C'est une façon d'utiliser le défilement TextBox en XAML et de l'utiliser comme zone de texte.

John
la source
1
La question est liée à TextBlocknon TextBox.
Afzaal Ahmad Zeeshan
Réponse pas tout à fait correcte, mais j'ai trouvé VerticalScrollBarVisibility comme un indice utile donc +1
Malachi
4

Cette réponse décrit une solution utilisant MVVM.

Cette solution est idéale si vous souhaitez ajouter une boîte de journalisation à une fenêtre, qui défile automatiquement vers le bas chaque fois qu'un nouveau message de journalisation est ajouté.

Une fois ces propriétés attachées ajoutées, elles peuvent être réutilisées n'importe où, ce qui en fait un logiciel très modulaire et réutilisable.

Ajoutez ce 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">

Ajoutez cette propriété attachée:

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
}

Et cette propriété jointe (pour vider la case):

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

Ensuite, si vous utilisez un framework d'injection de dépendances tel que MEF, vous pouvez placer tout le code spécifique à la journalisation dans son propre 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
}

Voici comment ça fonctionne:

  • Le ViewModel bascule les propriétés jointes pour contrôler le TextBox.
  • Comme il utilise "Append", c'est rapide comme l'éclair.
  • Tout autre ViewModel peut générer des messages de journalisation en appelant des méthodes sur le ViewModel de journalisation.
  • Comme nous utilisons le ScrollViewer intégré à la TextBox, nous pouvons le faire défiler automatiquement vers le bas de la zone de texte chaque fois qu'un nouveau message est ajouté.
Contango
la source
4
<ScrollViewer MaxHeight="50"  
              Width="Auto" 
              HorizontalScrollBarVisibility="Disabled"
              VerticalScrollBarVisibility="Auto">
     <TextBlock Text="{Binding Path=}" 
                Style="{StaticResource TextStyle_Data}" 
                TextWrapping="Wrap" />
</ScrollViewer>

Je fais cela d'une autre manière en mettant MaxHeight dans ScrollViewer.

Ajustez simplement le MaxHeight pour afficher plus ou moins de lignes de texte. Facile.

Tony Wu
la source
1

J'ai essayé de faire fonctionner ces suggestions pour un bloc de texte, mais je n'ai pas réussi à le faire fonctionner. J'ai même essayé de le faire fonctionner chez le designer. (Regardez dans Layout et développez la liste en cliquant sur la flèche vers le bas "V" en bas) J'ai essayé de régler le scrollviewer sur Visible puis sur Auto , mais cela ne fonctionnait toujours pas.

J'ai finalement abandonné et changé le TextBlocken un TextBoxavec l' ensemble d' attributs Readonly , et cela a fonctionné comme un charme.

Scott Bordelon
la source
0

Je ne sais pas si quelqu'un d'autre a ce problème, mais en l'enveloppant TextBlockdans un ScrollViewerquelque chose qui a gâché mon interface utilisateur - comme solution de contournement simple, j'ai compris que le remplacer TextBlockpar un TextBoxcomme celui-ci

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

crée un TextBoxqui ressemble et se comporte comme un TextBlockavec une barre de défilement (et vous pouvez tout faire dans le concepteur).

dunkleosteus
la source
0

Il s'agit d'une solution simple à cette question. Le défilement vertical ne sera activé que lorsque le texte déborde.

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

Zuhair
la source