Вопрос по itemscontrol, wpf, behavior, scroll – Как получить положение полосы прокрутки ItemsControl программно?

0

У меня есть прикрепленное поведение к ItemsControl, который прокручивается вниз, когда добавляется новый элемент. Поскольку я работаю над программой типа чата, я не хочу, чтобы она прокручивалась, если у пользователя есть полоса прокрутки где-либо, кроме самого нижнего, поскольку это было бы очень раздражающим в противном случае (некоторые программы чата делают это, и это ужасно).

Как мне это сделать? Я не знаю, как получить доступ к упаковочному ScrollViewer или каким-то иным образом выяснить, нужно ли мне его показывать или нет.

Это класс поведения, который я на самом деле получил от кого-то в StackOverflow. Я все еще учусь поведению.

public class ScrollOnNewItem : Behavior<ItemsControl>
{
    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;
    }

    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 (e.Action == NotifyCollectionChangedAction.Add)
        {
            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();
        }
    }
}

Ваш Ответ

2   ответа
1

Есть еще один способ реализовать это поведение. Этот способ проще, чем выше. Все, что вам нужно сделать, это вызвать метод, как показано ниже:

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

Подходит дляTextBox тоже.

Спасибо за этот ответ. Cowman
3

Хорошо, вот ответ, который я придумал для себя.

Я понял, что есть метод GetSelfAndAncestors для объектов зависимостей. Используя это, я могу получить предка ScrollViewer (если он есть) моего AssociatedObject (ItemsControl) и манипулировать им с этим.

Поэтому я добавил это поле в свое поведение

private ScrollViewer scrollViewer;
private bool isScrollDownEnabled;

И в обработчике событий OnLoaded я назначил его с помощью следующего кода

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

И в обработчике событий OnCollectionChanged я пошел дальше и обернул всю логику в оператор if следующим образом

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

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

Итак, все вместе, код выглядит следующим образом

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

Как сказано в комментариях, чтобы использовать поведение, мне просто нужно добавить новый xmlns в мой файл xaml области в коде, который содержит мое поведение.

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

Тогда на контроле я просто добавляю поведение.

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

i класс - это просто пространство имен интерактивности.xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

Спасибо за расширение вашего ответа! +1
Это довольно плохая реализация в том смысле, что размещение ItemsControl внутри ScrollViewer, как это имеет существенное влияние на производительность. Я бы не стал этого делать, если вы не знаете, что в вашем списке будет только несколько элементов.
Я пошел дальше и отредактировал дно, чтобы показать, как его использовать. Это просто вопрос добавления поведения к рассматриваемому элементу в представлении. Cowman
Было бы лучше, если бы вы предоставили код, как использовать этот класс!

Похожие вопросы