通过视图模型将滚动查看器滚动到顶部

发布于 2024-11-09 04:24:24 字数 561 浏览 0 评论 0原文

我将 ScrollViewer 与 MVVM 模式一起使用,ScrollViewer 包装了项目列表,例如

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

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

I am using the ScrollViewer with the MVVM pattern, and a list of items is wrapped by the ScrollViewer, such as

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

The items of the listview are bound to a collection of objects in the viewmodel. I want the scrollviewer to scroll to the top whenever a item is added or removed from the collection.
I need the viewmodel to trigger the event, rather than using the ScrollToTop() method in the code-behind of the view.

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

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

发布评论

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

评论(4

(り薆情海 2024-11-16 04:24:24

恕我直言,最清晰的方法是通过 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>

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

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

IMHO, the clearest way to do this is using a "Behavior" via an AttachedProperty. An AttachedProperty is a mechanism to extend existing controls functionality.

First, create a class to hold the AtachedProperty, for instance:

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

This attached property allows a ScrollViewer having "magically" a new property of type Boolean, acting like a DependencyProperty in your XAML. If you bind this property to a standard property in your ViewModel, for instance:

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

(again, the name is up to you) and then you set this Reset property to true, your ScrollViewer will scroll to top.
I have named the AtachedPropertyas AutoScrollToTop, but the name is not important for this purpose.

The XAML will be something like:

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

Note: my is the namespace where your ScrollViewerBehavior class lives. For example: xmlns:my="clr-namespace:MyApp.Behaviors"

Finally, the only thing you have to do in your ViewModel is to set Reset = true when you like, in your case, when you add or remove an element from the collection.

老旧海报 2024-11-16 04:24:24

创建一个新的 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);
        }
    }

Create a new ListView control which extend Listview and use this new one instead

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);
        }
    }
吐个泡泡 2024-11-16 04:24:24

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

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

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

I have also faced a similar scenario where I needed to assign ScrollViewer's HorizontalOffset and VerticalOffset programmatically. I am afraid there is no direct binding mechanism for this. What I did was a way around (believe me, I still do not like the approach I followed, but I did not find any other option). Here is what I suggest:

Hook the ScrollViewer's Loaded event, cast the sender object to ScrollViewer and assign it to a property in DataContext (Means you need to keep a ScrollViewer propery in DataContext which will hold the reference of ScrollViewer in the UI). Hook up ObservableCollection's CollectionChanged events in ViewModel and using the ScrollViewer property, you can call methods like ScrollToTop() etc.

This is just a way around. I am still looking for better solution.

爱殇璃 2024-11-16 04:24:24

在 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 滚动),您还可以将其实现为行为/附加属性,或扩展 ListView。这可能会变得有点复杂,但我鼓励您考虑一下这些选项。

The simplest correct way to do this in MVVM is by creating an event in your viewmodel and subscribing to it from your view. And then, in the event handler, call ScrollToTop.

You fire the event from your viewmodel every time your collection is modified, for instance, and then it's up to the view to react to that event and scroll the list to the top.

Even if this involves some code-behind and demands that the view knows part of its viewmodel, it doesn't violate the MVVM pattern, unlike other workarounds.

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

}

EDIT: But it can be refined a lot more, of course. If you want to avoid using code-behind, look for DataEventTriggers. If you don't mind about code-behind but are concerned about memory leaks, look for weak events.

And finally, since the logic you want is 100% view-related (have the ListView scroll every time an item is added or removed to it), you could also implement it as a Behavior / attached property, or extending the ListView. That could get a tad more convoluted, but I encourage you to give those options some thought.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文