Automatyczny pionowy pasek przewijania w WPF TextBlock?

335

Mam TextBlockWPF. Piszę do niego wiele linii, znacznie przekraczając jego wysokość pionową. Spodziewałem się, że pionowy pasek przewijania pojawi się automatycznie, gdy tak się stanie, ale tak się nie stało. Próbowałem poszukać właściwości paska przewijania w panelu Właściwości, ale nie mogłem jej znaleźć.

Jak mogę automatycznie utworzyć pionowy pasek przewijania, TextBlockgdy jego zawartość przekroczy wysokość?

Wyjaśnienie: Wolę to zrobić od projektanta, a nie pisząc bezpośrednio do XAML.

Bab Yogoo
źródło
1
Po ponownym przeczytaniu tego pytania zauważam, że wspomniałeś TextBlockdwa razy i TextBoxraz.
Drew Noakes,

Odpowiedzi:

555

Zawiń w przeglądarce przewijania:

<ScrollViewer>
    <TextBlock />
</ScrollViewer>

UWAGA ta odpowiedź dotyczy TextBlock(elementu tekstowego tylko do odczytu), zgodnie z pierwotnym pytaniem.

Jeśli chcesz pokazać paski przewijania w TextBox(edytowalnym elemencie tekstowym), użyj ScrollViewerdołączonych właściwości:

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

Prawidłowe wartości dla tych dwóch właściwości są Disabled, Auto, Hiddeni Visible.

Drew Noakes
źródło
2
Jak to zrobić od projektanta?
Bab Yogoo,
16
Niestety nie jestem pewien, nie używam projektanta WPF. Myślę, że jeśli dodasz XAML bezpośrednio, projektant sam się zaktualizuje.
Drew Noakes,
5
@conqenator TextBox.ScrollToEnd ();
Petey B
2
@Greg, pytanie dotyczy TextBlocknie TextBox.
Drew Noakes
7
Czasami potrzebna jest MaxHeight w Scrollviewer, aby wymusić pojawienie się scoll, jeśli element otaczający nie wymusza żadnej wysokości.
HackerBaloo,
106

może teraz korzystać z następujących opcji:

<TextBox Name="myTextBox" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True">SOME TEXT
</TextBox>
Vince
źródło
19
@ jjnguy, zinterpretowałem pierwotne pytanie jako dotyczące TextBlocknie TextBox(jak w tytule i linii początkowej), ale wspomnianego drugiego akapitu TextBox. Dla jasności, ta odpowiedź jest zdecydowanie najlepszym podejściem do pól tekstowych , a moje jest najlepsze, jakie znam dla bloków tekstowych :)
Drew Noakes
@Drew, ah, ma sens. Dziękuję za wyjaśnienie.
jjnguy
2
Dla mnie też działał lepiej. Przynajmniej w przypadku TextBox, gdy używasz ScrollViewer wokół niego, podobnie jak w zaakceptowanej odpowiedzi, ramki TextBox znikają, ponieważ cała kontrolka jest przewijana, a nie tylko jej zawartość.
Tankował
20

Coś lepszego byłoby:

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

Zapewnia to, że tekst w bloku tekstowym nie przepełnia się i nie zachodzi na elementy pod blokiem tekstowym, co może mieć miejsce, jeśli siatka nie jest używana. Stało się tak, gdy wypróbowałem inne rozwiązania, mimo że blok tekstowy znajdował się już w siatce z innymi elementami. Należy pamiętać, że szerokość bloku tekstu powinna wynosić Auto i należy określić żądaną za pomocą elementu Grid. Zrobiłem to w moim kodzie i działa pięknie. HTH.

varagrawal
źródło
7
<ScrollViewer Height="239" VerticalScrollBarVisibility="Auto">
    <TextBox AcceptsReturn="True" TextWrapping="Wrap" LineHeight="10" />
</ScrollViewer>

W ten sposób można użyć przewijanego TextBox w XAML i użyć go jako obszaru tekstowego.

Jan
źródło
1
Pytanie dotyczy TextBlocknie TextBox.
Afzaal Ahmad Zeeshan
Niezupełnie poprawna odpowiedź, ale uznałem VerticalScrollBarVisibility za przydatną wskazówkę, więc +1
Malachi
4

Ta odpowiedź opisuje rozwiązanie wykorzystujące MVVM.

To rozwiązanie jest świetne, jeśli chcesz dodać okno logowania do okna, które automatycznie przewija się w dół za każdym razem, gdy dodawana jest nowa wiadomość logowania.

Po dodaniu tych dołączonych właściwości można je ponownie wykorzystać w dowolnym miejscu, dzięki czemu powstaje bardzo modułowe oprogramowanie do wielokrotnego użytku.

Dodaj ten 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">

Dodaj tę dołączoną właściwość:

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
}

I ta załączona właściwość (aby wyczyścić pole):

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

Następnie, jeśli używasz struktury wstrzykiwania zależności, takiej jak MEF, możesz umieścić cały kod specyficzny dla rejestrowania w jego własnym 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
}

Oto jak to działa:

  • ViewModel przełącza Attached Properties w celu sterowania TextBox.
  • Ponieważ używa „Append”, błyskawicznie działa.
  • Każdy inny ViewModel może generować komunikaty rejestrowania, wywołując metody rejestrowania ViewModel.
  • Kiedy używamy ScrollViewer wbudowanego w TextBox, możemy go automatycznie przewijać do dołu pola tekstowego za każdym razem, gdy dodawana jest nowa wiadomość.
Contango
źródło
4
<ScrollViewer MaxHeight="50"  
              Width="Auto" 
              HorizontalScrollBarVisibility="Disabled"
              VerticalScrollBarVisibility="Auto">
     <TextBlock Text="{Binding Path=}" 
                Style="{StaticResource TextStyle_Data}" 
                TextWrapping="Wrap" />
</ScrollViewer>

Robię to w inny sposób, umieszczając MaxHeight w ScrollViewer.

Wystarczy dostosować MaxHeight, aby wyświetlić więcej lub mniej wierszy tekstu. Łatwy.

Tony Wu
źródło
1

Próbowałem przekonać te sugestie do działania dla bloku tekstowego, ale nie udało mi się go uruchomić. Próbowałem nawet przekonać projektanta do pracy. (Spójrz w Układ i rozwiń listę, klikając strzałkę w dół „V” u dołu) Próbowałem ustawić przeglądarkę przewijania na Widoczne, a następnie Auto , ale nadal nie działało.

I w końcu poddał się i zmienił TextBlockDo TextBoxz Readonly zestawu atrybutów, i to działało jak czar.

Scott Bordelon
źródło
0

Nie wiem, czy ktoś inny ma ten problem, ale zawijanie go TextBlockw jakiś ScrollViewersposób pomieszało mój interfejs - jako proste obejście doszedłem do wniosku, że zastąpienie TextBlockgo takim TextBoxjak ten

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

tworzy, TextBoxktóry wygląda i zachowuje się jak TextBlockz paskiem przewijania (i możesz to wszystko zrobić w projektancie).

dunkleosteus
źródło
0

To proste rozwiązanie tego pytania. Przewijanie w pionie zostanie aktywowane tylko wtedy, gdy tekst się przepełni.

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

Zuhair
źródło