c# - 使用 MVVM 在 WPF 中自动滚动 ListView

标签 c# wpf listview mvvm autoscroll

我尝试创建一个在添加新行时自动滚动到末尾的列表。

这是一个简单的 MVVM 示例,我想在其中集成:

有一个按钮可以在单击时将行添加到列表中。

型号代码:

public class Student
{
    public string Lastname {get; set;}
    public string Firstname {get; set;}

    public Student(string lastname, string firstname) {
        this.Lastname = lastname;
        this.Firstname = firstname;

    }
}

public class StudentsModel: ObservableCollection<Student>
{
    private static object _threadLock = new Object();
    private static StudentsModel current = null;

    public static StudentsModel Current {
        get {
            lock (_threadLock)
            if (current == null)
                current = new StudentsModel();

            return current;
        }
    }

    private StudentsModel() {

        for (int i = 1; i <= 50; i++)
        {
            Student aStudent = new Student("Student " + i.ToString(), "Student " + i.ToString());
            Add(aStudent);
        }
    }

    public void AddAStudent(String lastname, string firstname) {
        Student aNewStudent = new Student(lastname, firstname);
        Add(aNewStudent);
    }
}

View 模型代码:
public class MainViewModel : ViewModelBase
{

    public StudentsModel Students { get; set; }

    public MainViewModel()
    {
        Students = StudentsModel.Current;
    }

    private ICommand _AddStudent;
    public ICommand AddStudent
    {
        get
        {
            if (_AddStudent == null)
            {
                _AddStudent = new DelegateCommand(delegate()
                {
                    Students.AddAStudent("New Student lastname", "New Student firstname");
                });
            }

            return _AddStudent;
        }
    }

查看代码:
<Window x:Class="demo.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:demo.Commands">
<Grid>
    <ListView  Grid.Row="2" BorderBrush="White" ItemsSource="{Binding Path=Students}"
               HorizontalAlignment="Stretch">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Lastname" DisplayMemberBinding="{Binding Path=Lastname}" />
                <GridViewColumn Header="Firstname" DisplayMemberBinding="{Binding Path=Firstname}" />

            </GridView>
        </ListView.View>
    </ListView >
    <Button Content="Add" Command="{Binding AddStudent}" Margin="601.94,36.866,96.567,419.403" />
</Grid>

谢谢

最佳答案

我编写了一个简单的 AttachedProperty,用于在绑定(bind)的 ObservableCollection 更改时自动滚动到底部:

public class AutoScroller : Behavior<ScrollViewer>
{
    public object AutoScrollTrigger 
    {
        get { return (object)GetValue( AutoScrollTriggerProperty ); }
        set { SetValue( AutoScrollTriggerProperty, value ); }
    }

    public static readonly DependencyProperty AutoScrollTriggerProperty = 
        DependencyProperty.Register(  
            "AutoScrollTrigger", 
            typeof( object ), 
            typeof( AutoScroller ), 
            new PropertyMetadata( null, ASTPropertyChanged ) );

    private static void ASTPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs args )
    {
        var ts = d as AutoScroller;            
        if( ts == null )
            return;

        // must be attached to a ScrollViewer
        var sv = ts.AssociatedObject as ScrollViewer;

        // check if we are attached to an ObservableCollection, in which case we
        // will subscribe to CollectionChanged so that we scroll when stuff is added/removed
        var ncol = args.NewValue as INotifyCollectionChanged;
        // new event handler
        if( ncol != null )
            ncol.CollectionChanged += ts.OnCollectionChanged;

        // remove old eventhandler
        var ocol = args.OldValue as INotifyCollectionChanged;
        if( ocol != null )
            ocol.CollectionChanged -= ts.OnCollectionChanged;


        // also scroll to bottom when the bound object itself changes
        if( sv != null && ts.AutoScroll )
            sv.ScrollToBottom();
    }

    private void OnCollectionChanged(object sender, EventArgs args)
    {
        App.Current.Dispatcher.Invoke(delegate {
            (this.AssociatedObject as ScrollViewer).ScrollToBottom();
        });
    }
}

注意:我使用 Rx 订阅 CollectionChanged 事件,但这可以通过正常的 .NET 事件处理加上 Dispatcher.Invoke 来获得 UI 线程上的 .ScrollToBottom() 调用来完成。

另请注意:此附加属性位于名为 TouchScroller 的 Behavior 类中,该类也可以执行其他操作,但可以将其简化为仅是一个简单的附加属性。

编辑:

要使用它,在 xaml 中,您只需绑定(bind) View 模型中的属性:
<ScrollViewer ...>
    <i:Interaction.Behaviors>
        <util:TouchScroller AutoScrollTrigger="{Binding Students}" />
    </i:Interaction.Behaviors>
    ...
</ScrollViewer>

EDIT2:

我编辑了代码以包含完整的行为。我使用 Behavior 而不是简单的带有附加行为的静态类,因为它可以访问 .AssociatedObject ,也就是需要调用.ScrollToBottom()的ScrollViewer在。如果不使用行为,您将不得不手动跟踪这些对象。我还删除了 Rx 订阅并添加了简单的事件处理程序和 Dispatcher.Invoke。

这是我使用的精简和修改版本,但我还没有测试过这个版本。

关于c# - 使用 MVVM 在 WPF 中自动滚动 ListView,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23963315/

相关文章:

c# - 你有一个简单但有用的例子来理解委托(delegate)的目的吗?

c# - 将对象集合连接成逗号分隔的字符串

android - 在 ListView 中显示 EditText 时如何隐藏多个光标?

java - 带有包含复选框的自定义适配器的 ListView

android - Activity 中的 fragment 在 ListView 的 setAdapter 中获取上下文 null

c# - Umbraco 7 中的表面 Controller 还是自定义 Controller ?

c# - 为什么在将项目转换为对象时,equals 的作用不同?

c# - ListView 未更新项目源 ObservableCollection 项目属性更改

c# - 在 WPF 中重用 ToggleButton 样式

WPF - 使用变量/参数创建可重用样式