How to get ItemsControl scrollbar position programmatically?

江枫思渺然 提交于 2019-12-02 10:24:01

Okay, here's the answer I came up with for myself.

I figured out that there is a GetSelfAndAncestors method for dependency objects. Using that, I am able to get the ScrollViewer ancestor(if there is one) of my AssociatedObject(the ItemsControl) and manipulate it with that.

So I added this field to my behavior

private ScrollViewer scrollViewer;
private bool isScrollDownEnabled;

And in the OnLoaded event handler I assigned it with the following code

scrollViewer = AssociatedObject.GetSelfAndAncestors().Where(a => a.GetType().Equals(typeof(ScrollViewer))).FirstOrDefault() as ScrollViewer;

And in the OnCollectionChanged event handler, I went ahead and wrapped all the logic in an if statement as follows

        if (scrollViewer != null)
        {
            isScrollDownEnabled = scrollViewer.ScrollableHeight > 0 && scrollViewer.VerticalOffset + scrollViewer.ViewportHeight < scrollViewer.ExtentHeight;

            if (e.Action == NotifyCollectionChangedAction.Add && !isScrollDownEnabled)
            {
                 // Do stuff
            }
        }

So all together, the code looks like the following

public class ScrollOnNewItem : Behavior<ItemsControl>
{
    private ScrollViewer scrollViewer;
    private bool isScrollDownEnabled;

    protected override void OnAttached()
    {
        AssociatedObject.Loaded += OnLoaded;
        AssociatedObject.Unloaded += OnUnLoaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= OnLoaded;
        AssociatedObject.Unloaded -= OnUnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged += OnCollectionChanged;
        scrollViewer = AssociatedObject.GetSelfAndAncestors().Where(a => a.GetType().Equals(typeof(ScrollViewer))).FirstOrDefault() as ScrollViewer;
    }

    private void OnUnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged -= OnCollectionChanged;
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (scrollViewer != null)
        {
            isScrollDownEnabled = scrollViewer.ScrollableHeight > 0 && scrollViewer.VerticalOffset + scrollViewer.ViewportHeight < scrollViewer.ExtentHeight;

            if (e.Action == NotifyCollectionChangedAction.Add && !isScrollDownEnabled)
            {
                int count = AssociatedObject.Items.Count;
                if (count == 0)
                    return;

                var item = AssociatedObject.Items[count - 1];

                var frameworkElement = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
                if (frameworkElement == null) return;

                frameworkElement.BringIntoView();
            }
        }
    }
}

As asked in the comments, to use a behavior, I just need to add a new xmlns to my xaml file of the area in code that contains my behavior.

             xmlns:behaviors="clr-namespace:Infrastructure.Behaviors;assembly=Infrastructure"

Then on the control I just add on the behavior.

    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl Name="Blah" ItemsSource="{Binding Messages}" ItemTemplate="{StaticResource MessageTemplate}">
            <i:Interaction.Behaviors>
                <behaviors:ScrollOnNewItem />
            </i:Interaction.Behaviors>
        </ItemsControl>
    </ScrollViewer>

The i class is just the interactivity namespace. xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

There is another way to implement this behavior. This way is easier than above. All you should do is invoke a method like below:

public void AppendText(RichTextBox richTextBox, string data){       
   richTextBox.AppendText(data);
   bool isScrollDownEnabled = richTextBox.VerticalOffset == 0 ||
        richTextBox.VerticalOffset + richTextBox.ViewportHeight == richTextBox.ExtentHeight;
   if (isScrollDownEnabled)
       richTextBox.ScrollToEnd();
}

It is suitable for TextBox too.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!