【问题标题】:Scroll the scrollviewer to top through viewmodel通过视图模型将滚动查看器滚动到顶部
【发布时间】:2011-08-30 14:53:48
【问题描述】:

我用的是MVVM模式的ScrollViewer,ScrollViewer封装了一个item的列表,比如

<ScrollViewer>
  <ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn
                Header = "Name"
                DisplayMemberBinding="{Binding Path=Name}"
            />              
        </GridView>
    </ListView.View>
  </ListView>
</ScrollViewer>

列表视图的项目绑定到视图模型中的对象集合。每当从集合中添加或删除项目时,我希望滚动查看器滚动到顶部。
我需要视图模型来触发事件,而不是在视图的代码隐藏中使用ScrollToTop() 方法。

【问题讨论】:

    标签: wpf mvvm scroll


    【解决方案1】:

    恕我直言,最清晰的方法是通过AttachedProperty 使用“行为”。 AttachedProperty 是一种扩展现有控件功能的机制。

    首先,创建一个类来保存AtachedProperty,例如:

    public class ScrollViewerBehavior
    {
        public static bool GetAutoScrollToTop(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoScrollToTopProperty);
        }
    
        public static void SetAutoScrollToTop(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollToTopProperty, value);
        }
    
        public static readonly DependencyProperty AutoScrollToTopProperty =
            DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) =>
                {
                    var scrollViewer = o as ScrollViewer;
                    if (scrollViewer == null)
                    {
                        return;
                    }
                    if ((bool)e.NewValue)
                    {
                        scrollViewer.ScrollToTop();
                        SetAutoScrollToTop(o, false);
                    }
                }));
    }
    

    此附加属性允许ScrollViewer“神奇地”拥有Boolean 类型的新属性,就像您的XAML 中的DependencyProperty。如果将此属性绑定到 ViewModel 中的标准属性,例如:

    private bool _reset;
    public bool Reset
    {
        get { return _reset; }
        set
        {
            _reset = value;
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Reset"));
        }
    }
    

    (同样,名称由您决定)然后您将此Reset 属性设置为true,您的ScrollViewer 将滚动到顶部。 我已将AtachedProperty命名为 AutoScrollToTop,但名称对于此目的并不重要。

    XAML 将类似于:

    <ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}">
        <ListView>
            <ListView.View>
                <GridView>
                    <GridViewColumn
                        Header = "Name"
                        DisplayMemberBinding="{Binding Path=Name}"
                    />
                </GridView>
            </ListView.View>
        </ListView>
    </ScrollViewer>
    

    注意:my 是您的 ScrollViewerBehavior 类所在的命名空间。例如:xmlns:my="clr-namespace:MyApp.Behaviors"

    最后,您在 ViewModel 中唯一需要做的就是在您喜欢时设置Reset = true,在您的情况下,当您从集合中添加或删除元素时。

    【讨论】:

    • 我喜欢你所说的“最清晰”而不是“最简单”的方式。 WPF + MVVM 有很多这样的。
    • 这真是太好了。谢谢!。是的,我同意 JumpingJezza,如果你不需要使用 MVVM,这真的很简单,但如果你需要使用 MVVM,那就非常复杂了。
    • 非常感谢您的回答。作为一个 wpf 新手,这个答案让我在大量阅读该主题后对行为有了更多的了解
    • SetAutoScrollToTop(o, false); 不适用于视图模型绑定。如果我设置为 true - Reset 使用后未设置为 false。
    【解决方案2】:

    在 MVVM 中最简单的正确方法是在您的视图模型中创建一个事件并从您的视图中订阅它。然后,在事件处理程序中,调用ScrollToTop

    例如,每次修改集合时,您都会从视图模型中触发事件,然后由视图来响应该事件并将列表滚动到顶部。

    即使这涉及一些代码隐藏并要求视图知道其视图模型的一部分,它也不会违反 MVVM 模式,这与其他解决方法不同。

    public interface IMyViewModel
    {
        event EventHandler MyCollectionChanged;
    }
    
    public class MyViewModel : IMyViewModel
    {
        public event EventHandler MyCollectionChanged;
    
        // More viewmodel related stuff
    
        protected virtual void OnMyCollectionChanged(EventArgs e)
        {
            if (MyCollectionChanged != null)
                MyCollectionChanged(this, e);
        }
    }
    
    public class MyWindow : Window
    {
        public MyWindow(IMyViewModel viewModel)
        {
            this.DataContext = viewModel;
            InitializeComponent();
            (this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler;
        }
    
        private void MyCollectionChangedEventHandler(object sender, EventArgs e)
        {
            // Do view related stuff
            scrollViewer.ScrollToTop();
        }
    
    }
    

    编辑:当然,它还可以进一步完善。如果您想避免使用代码隐藏,请查找 DataEventTriggers。如果您不介意代码隐藏但担心内存泄漏,请查找弱事件。

    最后,由于您想要的逻辑是 100% 与视图相关的(每次添加或删除项目时让 ListView 滚动),您还可以将其实现为 Behavior / 附加属性,或扩展 ListView .这可能会更令人费解,但我鼓励您考虑一下这些选项。

    【讨论】:

    • 我是 MVVM 的新手——在你的第一个选项中,如果你能展示一些关于如何实现事件 + 订阅的代码,那就太好了。是不是像this?我在某处读到了一个弱事件模式(无论是什么),以防止内存泄漏。
    • 我添加了一些代码示例,使用最简单的 MVVM 方法解决您的问题,当然,它还可以进一步改进。如果您想避免使用代码隐藏,请查找 DataEventTriggers。如果您不介意代码隐藏但担心内存泄漏,请查找弱事件。
    • 最后,由于您想要的逻辑是 100% 与视图相关的(每次添加或删除项目时让 ListView 滚动),您还可以将其实现为 Behavior / 附加属性,或扩展 ListView。这可能会更令人费解,但我鼓励您考虑这些选项。
    • 正如 almulo 指出的那样,行为/附加属性方法是解决您的问题的有效且简单的方法。看看我的回答stackoverflow.com/a/30932716/1351315
    【解决方案3】:

    创建一个新的 ListView 控件,它扩展 Listview 并改用这个新控件

    public class ScrollListView : ListView
        {
            protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
    
                if (e.OldItems.Count > 0)
                    this.ScrollIntoView(e.OldItems[e.OldStartingIndex]);
    
                base.OnItemsChanged(e);
            }
        }
    

    【讨论】:

    • 这是一个不错的方法。但事实上,它的作用与@D.Young 的要求完全不同。他希望 ScrollViewer 在添加或删除项目时滚动到顶部。
    • 不错的主意!在我的场景中,我有一个 Grid + ItemControls + 一个折叠面板 + 几个按钮,而不是一个简单的 ListView,所以我将赏金奖励给 @antiocol,因为他的回答对我来说效果更好。该解决方案可能适合 OP 的原始问题。
    【解决方案4】:

    我也遇到过类似的情况,我需要以编程方式分配 ScrollViewer 的 Horizo​​ntalOffset 和 VerticalOffset。恐怕没有直接的绑定机制。我所做的是一种解决方法(相信我,我仍然不喜欢我遵循的方法,但我没有找到任何其他选择)。这是我的建议:

    挂钩 ScrollViewer 的 Loaded 事件,将 sender 对象转换为 ScrollViewer 并将其分配给 DataContext 中的属性(意味着您需要在 DataContext 中保留一个 ScrollViewer 属性,该属性将在 UI 中保存 ScrollViewer 的引用)。在 ViewModel 中连接 ObservableCollection 的 CollectionChanged 事件,并使用 ScrollViewer 属性,您可以调用 ScrollToTop() 等方法。

    这只是一种解决方法。我仍在寻找更好的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-06
      相关资源
      最近更新 更多