如何控制 MVVM WPF 应用程序中列表框的滚动位置

发布于 2024-08-22 21:24:32 字数 319 浏览 8 评论 0原文

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

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

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.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

恍梦境° 2024-08-29 21:24:32

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

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

从我的虚拟机中,我可以获得默认集合视图并使用 MoveCurrent*()< 之一/code> 方法来确保正在编辑的项目是当前项目。

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

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

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

    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.

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

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

瘫痪情歌 2024-08-29 21:24:32

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

然后在视图广告中在顶部引用此引用:

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

并执行以下操作:

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

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

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

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.

∝单色的世界 2024-08-29 21:24:32

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

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

用法:

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

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">
最冷一天 2024-08-29 21:24:32

如果上面的代码不适合您,请尝试一下

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

}

XAML 中的用法

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

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

<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>
酒绊 2024-08-29 21:24:32

下面是我对 @VahidN 建议的解决方案的 ListView 改编。我希望这可以帮助正在寻找类似解决方案的人。

namespace YourSolutionName.YourProjectName.Utilities
{
    public class ListViewExtension
    {
        public static readonly DependencyProperty AutoScrollToCurrentItemProperty =
            DependencyProperty.RegisterAttached("AutoScrollToCurrentItem",
                typeof(bool), typeof(ListViewExtension),
                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 listView = (ListView)obj;
            var newValue = (bool)e.NewValue;

            if (listView == null) return;

            if (newValue)
                listView.SelectionChanged += ListViewSelectionChanged;
            else
                listView.SelectionChanged -= ListViewSelectionChanged;
        }

        static void ListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var listView = (ListView)sender;
            if (listView == null || listView.SelectedItem == null || listView.Items == null) return;

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

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

将命名空间添加到您的 Xaml 文件:

xmlns:ut="clr-namespace:YourSolutionName.YourProjectName.Utilities"

Xaml 标记中的用法:

<ListView
    x:Name="CollectioListView"
    ut:ListViewExtension.AutoScrollToCurrentItem="True"
    ItemsSource="{Binding YourListViewCollection}"
    ScrollViewer.HorizontalScrollBarVisibility="Auto"
    ScrollViewer.VerticalScrollBarVisibility="Auto"
    SelectedItem="{Binding Path=CurrentSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

请注意,以下属性和值是可选的:

a) x:Name="CollectioListView"
b) ScrollViewer.HorizontalScrollBarVisibility="Auto"
c) ScrollViewer.VerticalScrollBarVisibility="Auto"
d) SelectedItem="{Binding Path=CurrentSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

Below is my ListView adaptation of the solution suggested by @VahidN. I hope this help somebody looking for a similar solution.

namespace YourSolutionName.YourProjectName.Utilities
{
    public class ListViewExtension
    {
        public static readonly DependencyProperty AutoScrollToCurrentItemProperty =
            DependencyProperty.RegisterAttached("AutoScrollToCurrentItem",
                typeof(bool), typeof(ListViewExtension),
                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 listView = (ListView)obj;
            var newValue = (bool)e.NewValue;

            if (listView == null) return;

            if (newValue)
                listView.SelectionChanged += ListViewSelectionChanged;
            else
                listView.SelectionChanged -= ListViewSelectionChanged;
        }

        static void ListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var listView = (ListView)sender;
            if (listView == null || listView.SelectedItem == null || listView.Items == null) return;

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

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

Add the namespace to your Xaml file:

xmlns:ut="clr-namespace:YourSolutionName.YourProjectName.Utilities"

Usage in Xaml Markup:

<ListView
    x:Name="CollectioListView"
    ut:ListViewExtension.AutoScrollToCurrentItem="True"
    ItemsSource="{Binding YourListViewCollection}"
    ScrollViewer.HorizontalScrollBarVisibility="Auto"
    ScrollViewer.VerticalScrollBarVisibility="Auto"
    SelectedItem="{Binding Path=CurrentSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

Please note that the following properties and values are optional:

a) x:Name="CollectioListView"
b) ScrollViewer.HorizontalScrollBarVisibility="Auto"
c) ScrollViewer.VerticalScrollBarVisibility="Auto"
d) SelectedItem="{Binding Path=CurrentSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文