C# 如何在 MVVM WPF 应用程序中控制 ListBox 的滚动位置

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2292360/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 01:08:31  来源:igfitidea点击:

How to control the scroll position of a ListBox in a MVVM WPF app

c#wpfmvvmlistboxscrollviewer

提问by Jobi Joy

I have got a big ListBox with vertical scrolling enabled, my MVVM has New and Edit ICommands. I am adding new item to the end of the collection but I want the scrollbar also to auto position to the End when I call my MVVM-AddCommand. I am also making an item editable(By calling EditCommand with a particular row item) from some other part of the application so that my ListBoxItem getting in to edit mode using DataTrigger, but how will I bring that particular row(ListBoxItem) to the view by adjusting the scroll position.

我有一个启用了垂直滚动的大列表框,我的 MVVM 有新建和编辑 ICommands。我正在向集合的末尾添加新项目,但我希望滚动条也能在调用 MVVM-AddCommand 时自动定位到 End。我还在应用程序的其他部分使项目可编辑(通过使用特定行项目调用 EditCommand),以便我的 ListBoxItem 使用 DataTrigger 进入编辑模式,但是我将如何将该特定行(ListBoxItem)带到视图中通过调整滚动位置。

If I am doing it in the View side I can call listBox.ScrollInToView(lstBoxItem). But what is the best way to solve this common Scroll issue from an MVVM perspective.

如果我在视图端执行此操作,我可以调用 listBox.ScrollInToView(lstBoxItem)。但是从 MVVM 的角度解决这个常见的 Scroll 问题的最佳方法是什么。

采纳答案by Dr. WPF

I typically set IsSynchronizedWithCurrentItem="True"on the ListBox. Then I add a SelectionChangedhandler and always bring the selected item into view, with code like this:

我通常设置IsSynchronizedWithCurrentItem="True"ListBox. 然后我添加一个SelectionChanged处理程序并始终将所选项目带入视图,代码如下:

    private void BringSelectionIntoView(object sender, SelectionChangedEventArgs e)
    {
        Selector selector = sender as Selector;
        if (selector is ListBox)
        {
            (selector as ListBox).ScrollIntoView(selector.SelectedItem);
        }
    }

From my VM I can get the default collection view and use one of the MoveCurrent*()methods to ensure that the item being edited is the current item.

从我的 VM 中,我可以获得默认的集合视图并使用其中一种MoveCurrent*()方法来确保正在编辑的项目是当前项目。

CollectionViewSource.GetDefaultView(_myCollection).MoveCurrentTo(thisItem);

NOTE: Edited to use ListBox.ScrollIntoView()to accomodate virtualization

注意:编辑ListBox.ScrollIntoView()用于适应虚拟化

回答by Kelly

Using this in MVVM can be easily accomplished via an attached behavior like so:

在 MVVM 中使用它可以通过附加的行为轻松完成,如下所示:

using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Jarloo.Sojurn.Behaviors
{
    public sealed class ScrollIntoViewBehavior : Behavior<ListBox>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.SelectionChanged += ScrollIntoView;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.SelectionChanged -= ScrollIntoView;
            base.OnDetaching();
        }

        private void ScrollIntoView(object o, SelectionChangedEventArgs e)
        {
            ListBox b = (ListBox) o;
            if (b == null)
                return;
            if (b.SelectedItem == null)
                return;

            ListBoxItem item = (ListBoxItem) ((ListBox) o).ItemContainerGenerator.ContainerFromItem(((ListBox) o).SelectedItem);
            if (item != null) item.BringIntoView();
        }
    }
}

Then in the View ad this reference at the top:

然后在查看广告顶部的这个参考:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

And do this:

并这样做:

<ListBox ItemsSource="{Binding MyData}" SelectedItem="{Binding MySelectedItem}">
         <i:Interaction.Behaviors>
             <behaviors:ScrollIntoViewBehavior />
         </i:Interaction.Behaviors>
</ListBox>

Now when the SelectedItem changes the behavior will do the BringIntoView() call for you.

现在,当 SelectedItem 更改时,该行为将为您执行 BringIntoView() 调用。

回答by c_wiz_kid

If the above code doesn't work for you, give this a try

如果上面的代码不适合你,试试这个

public class ListBoxExtenders : DependencyObject
{
    public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));

    public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollToSelectedItemProperty);
    }

    public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollToSelectedItemProperty, value);
    }

    public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
    {
        var listBox = s as ListBox;
        if (listBox != null)
        {
            var listBoxItems = listBox.Items;
            if (listBoxItems != null)
            {
                var newValue = (bool)e.NewValue;

                var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));

                if (newValue)
                    listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker;
                else
                    listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker;
            }
        }
    }

    public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
    {
        if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
            listBox.ScrollIntoView(listBox.Items[index]);
    }

}

Usage in XAML

XAML 中的用法

<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>

回答by VahidN

This is the attached property form of the accepted answer:

这是已接受答案的附加属性形式:

using System.Windows;
using System.Windows.Controls;

namespace CommonBehaviors
{
    public static class ScrollCurrentItemIntoViewBehavior
    {
        public static readonly DependencyProperty AutoScrollToCurrentItemProperty =
            DependencyProperty.RegisterAttached("AutoScrollToCurrentItem",
                typeof(bool), typeof(ScrollCurrentItemIntoViewBehavior),
                new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));

        public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoScrollToCurrentItemProperty);
        }

        public static void OnAutoScrollToCurrentItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var listBox = obj as ListBox;
            if (listBox == null) return;

            var newValue = (bool)e.NewValue;
            if (newValue)
                listBox.SelectionChanged += listBoxSelectionChanged;
            else
                listBox.SelectionChanged -= listBoxSelectionChanged;
        }

        static void listBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var listBox = sender as ListBox;
            if (listBox == null || listBox.SelectedItem == null || listBox.Items == null) return;

            listBox.Items.MoveCurrentTo(listBox.SelectedItem);
            listBox.ScrollIntoView(listBox.SelectedItem);
        }

        public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollToCurrentItemProperty, value);
        }
    }
}

Usage:

用法:

<ListBox ItemsSource="{Binding}"
          IsSynchronizedWithCurrentItem="True"
          behaviors:ScrollCurrentItemIntoViewBehavior.AutoScrollToCurrentItem="True">