如何以编程方式获取ItemsControl滚动条位置?

时间:2012-09-03 23:28:07

标签: wpf scroll itemscontrol behavior

我对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 个答案:

答案 0 :(得分:3)

好的,这是我为自己想出的答案。

我发现依赖对象有一个GetSelfAndAncestors方法。使用它,我能够获得我的AssociatedObject(ItemsControl)的ScrollViewer祖先(如果有的话)并用它来操作它。

所以我将此字段添加到我的行为

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

正如评论中所说,要使用行为,我只需要在包含我行为的代码中向区域的xaml文件中添加一个新的xml。

             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:ⅰ= “CLR-名称空间:System.Windows.Interactivity;装配= System.Windows.Interactivity”

答案 1 :(得分: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