ti-enxame.com

Como rolar para o fundo de um ScrollViewer automaticamente com Xaml e ligação?

Eu tenho um TextBlock cujo conteúdo é dados vinculados a uma propriedade de seqüência de caracteres do ViewModel. Este TextBlock tem um ScrollViewer em volta dele.

O que eu quero fazer é toda vez que os logs mudam, o ScrollViewer vai rolar até o final. Idealmente eu quero algo assim:

    <ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto"
                  ScrollPosition="{Binding Path=ScrollPosition}">
        <TextBlock Text="{Binding Path=Logs}"/>
    </ScrollViewer>

Eu não quero usar Code Behind! A solução que estou procurando deve estar usando only binding e/ou Xaml.

35
JiBéDoublevé

Você pode criar uma propriedade anexada ou um comportamento para obter o que deseja sem usar o código. De qualquer forma, você ainda precisará escrever algum código.

Aqui está um exemplo de uso da propriedade anexada.

Propriedade anexada

public static class Helper
{
    public static bool GetAutoScroll(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollProperty);
    }

    public static void SetAutoScroll(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollProperty, value);
    }

    public static readonly DependencyProperty AutoScrollProperty =
        DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(Helper), new PropertyMetadata(false, AutoScrollPropertyChanged));

    private static void AutoScrollPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var scrollViewer = d as ScrollViewer;

        if (scrollViewer != null && (bool)e.NewValue)
        {
            scrollViewer.ScrollToBottom();
        }
    }
}

Ligação Xaml

<ScrollViewer local:Helper.AutoScroll="{Binding IsLogsChangedPropertyInViewModel}" .../>

Você precisará criar uma propriedade booleana IsLogsChangedPropertyInViewModel e defini-la como true quando a propriedade da string for alterada.

Espero que isto ajude! :)

44
Justin XL

Resposta atualizada 2017-12-13, agora usa o evento ScrollChanged e verifica se o tamanho da extensão é alterado. Mais confiável e não interfere na rolagem manual

Eu sei que esta questão é antiga, mas tenho uma implementação melhorada:

  • Nenhuma dependência externa
  • Você só precisa definir a propriedade uma vez

O código é fortemente influenciado pelas soluções do Justin XL e da Contango

public static class AutoScrollBehavior
{
    public static readonly DependencyProperty AutoScrollProperty =
        DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollBehavior), new PropertyMetadata(false, AutoScrollPropertyChanged));


    public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        var scrollViewer = obj as ScrollViewer;
        if(scrollViewer != null && (bool)args.NewValue)
        {
            scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
            scrollViewer.ScrollToEnd();
        }
        else
        {
            scrollViewer.ScrollChanged-= ScrollViewer_ScrollChanged;
        }
    }

    private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        // Only scroll to bottom when the extent changed. Otherwise you can't scroll up
        if (e.ExtentHeightChange != 0)
        {
            var scrollViewer = sender as ScrollViewer;
            scrollViewer?.ScrollToBottom();
        }
    }

    public static bool GetAutoScroll(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollProperty);
    }

    public static void SetAutoScroll(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollProperty, value);
    }
}

Uso:

<ScrollViewer n:AutoScrollBehavior.AutoScroll="True" > // Where n is the XML namespace 
21
Roy T.

De Blog de Geoff no ScrollViewer AutoScroll Behavior .

Adicione esta classe:

namespace MyAttachedBehaviors
{
    /// <summary>
    ///     Intent: Behavior which means a scrollviewer will always scroll down to the bottom.
    /// </summary>
    public class AutoScrollBehavior : Behavior<ScrollViewer>
    {
        private double _height = 0.0d;
        private ScrollViewer _scrollViewer = null;

        protected override void OnAttached()
        {
            base.OnAttached();

            this._scrollViewer = base.AssociatedObject;
            this._scrollViewer.LayoutUpdated += new EventHandler(_scrollViewer_LayoutUpdated);
        }

        private void _scrollViewer_LayoutUpdated(object sender, EventArgs e)
        {
            if (Math.Abs(this._scrollViewer.ExtentHeight - _height) > 1)
            {
                this._scrollViewer.ScrollToVerticalOffset(this._scrollViewer.ExtentHeight);
                this._height = this._scrollViewer.ExtentHeight;
            }
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (this._scrollViewer != null)
            {
                this._scrollViewer.LayoutUpdated -= new EventHandler(_scrollViewer_LayoutUpdated);
            }
        }
    }
}

Este código depende do Blend Behaviors, que requer uma referência a System.Windows.Interactivity. Veja ajuda para adicionar System.Windows.Interactivity .

Se você instalar o pacote NuGet do MVVM Light, poderá incluir uma referência aqui:

packages\MvvmLightLibs.4.2.30.0\lib\net45\System.Windows.Interactivity.dll

Certifique-se de que você tenha esta propriedade em seu cabeçalho, que aponta para System.Windows.Interactivity.dll:

xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"

Adicione um comportamento de mistura na ScrollViewer:

<i:Interaction.Behaviors>
    <implementation:AutoScrollBehavior />
</i:Interaction.Behaviors>

Exemplo:

<GroupBox Grid.Row="2" Header ="Log">
    <ScrollViewer>
        <i:Interaction.Behaviors>
            <implementation:AutoScrollBehavior />
        </i:Interaction.Behaviors>
        <TextBlock Margin="10" Text="{Binding Path=LogText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap"/>
    </ScrollViewer>
</GroupBox> 

Temos que adicionar uma definição para o namespace, senão não saberá onde encontrar a classe C # que acabamos de adicionar. Adicione esta propriedade na tag <Window>. Se você estiver usando o ReSharper, ele irá sugerir isso automaticamente para você.

xmlns:implementation="clr-namespace:MyAttachedBehaviors"

Agora, se tudo correr bem, o texto na caixa irá sempre rolar para baixo.

O exemplo XAML fornecido imprimirá o conteúdo da propriedade ligada LogText na tela, o que é perfeito para o registro em log.

11
Contango

É fácil, exemplos:

yourContronInside.ScrollOwner.ScrollToEnd (); 
yourContronInside.ScrollOwner.ScrollToBottom ();
5
Edilberto Sánchez Forero

Aqui está uma pequena variação.

Isso irá rolar para baixo quando a altura do visualizador de rolagem (viewport) e a altura do conteúdo (extensão) do apresentador de rolagem mudar. 

É baseado na resposta de Roy T, mas eu não pude comentar, então eu postei como resposta.

    public static class AutoScrollHelper
    {
        public static readonly DependencyProperty AutoScrollProperty =
            DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollHelper), new PropertyMetadata(false, AutoScrollPropertyChanged));


        public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            var scrollViewer = obj as ScrollViewer;
            if (scrollViewer == null) return;

            if ((bool) args.NewValue)
            {
                scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
                scrollViewer.ScrollToEnd();
            }
            else
            {
                scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
            }
        }

        static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            // Remove "|| e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0" if you want it to only scroll to the bottom when it increases in size
            if (e.ViewportHeightChange > 0 || e.ExtentHeightChange > 0 || e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0)
            {
                var scrollViewer = sender as ScrollViewer;
                scrollViewer?.ScrollToEnd();
            }
        }

        public static bool GetAutoScroll(DependencyObject obj)
        {
            return (bool) obj.GetValue(AutoScrollProperty);
        }

        public static void SetAutoScroll(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollProperty, value);
        }
    }
0
Troto