测试当前条目是 CollectionViewSource 中的第一个还是最后一个条目

Test if the current entry is the first or last entry in a CollectionViewSource

我正在通过 Entity Framework 从 MS SQL 服务器 table 读取行到 C# CollectionViewSource 中。一行 = 一个集合条目。

我使用数据绑定将每个 CollectionViewSource 条目的数据元素连接到 WPF GUI 的控件。 用户使用 GUI 上的按钮使用如下命令处理程序在集合条目中向后和向前翻页。

    private void DisplayNextRecordButtonCommandHandler(object sender, ExecutedRoutedEventArgs e)               //  Select the Next record for Display.
    {
        MyCollectionViewSource.View.MoveCurrentToNext();
        //Prevent the display of an "empty" record
        if (MyCollectionViewSource.View.IsCurrentAfterLast)
        {
            orgUnitAssetRskViewSource.View.MoveCurrentToPrevious();
        }
        selectedRecordPosition = orgUnitAssetRskViewSource.View.CurrentPosition;
    }

在我开始在我的 GUI ComboBox 和 Text 控件中包含 "SelectionChanged" 和 "TextChanged" 事件之前,一切都运行良好。 当我移动到集合中的下一个或上一个条目时,这些事件会触发。 一切正常,直到我到达集合中的第一个或最后一个条目。

"IsCurrentAfterLast" 测试并没有阻止我翻阅集合中的最后一个条目,当我这样做时,我得到一个 "Object reference not set to an instance of an object" 异常。 我假设异常是在 "SelectionChanged" 和 "TextChanged" 事件在第一个或最后一个集合条目之前遇到虚假数据时引起的。

在没有像 "IsCurrentFirst" 和 "IsCurrentLast" 这样巧妙的东西的情况下,任何人都可以建议一种有效的方法来计算集合中的条目,这样我就可以避免超过第一个和最后一个吗?

In the absence of something slick like "IsCurrentFirst" and "IsCurrentLast"

足够简单,可以在 ICollectionView 抽象上创建一些扩展方法来提供所需的功能

public static class CollectionViewExtensions {

    public static bool IsCurrentFirst(this ICollectionView view) {
        return view.CurrentItem != null && view.CurrentPosition == 0;
    }

    public static bool IsCurrentLast(this ICollectionView view) {
        if (view.CurrentItem == null) return false;
        var index = view.CurrentPosition;
        var max = view.Count() - 1;
        return index == max;
    }

    public static bool CanMoveCurrentToNext(this ICollectionView view) {
        return !view.IsCurrentLast();
    }

    public static bool CanMoveCurrentToPrevious(this ICollectionView view) {
        return !view.IsCurrentFirst();
    }

    static int Count(this ICollectionView source) {
        int count = 0;
        var e = source.GetEnumerator();
        checked {
            while (e.MoveNext()) count++;
        }
        return count;
    }
}

扩展方法现在应该允许进行此类检查。

创建一些可以直接挂接到上一个和下一个按钮的派生 ICommand 实现。

MoveCurrentToNextCommand

public class MoveCurrentToNextCommand : ICommand {
    private readonly ICollectionView view;

    public MoveCurrentToNextCommand(ICollectionView view) {
        this.view = view;
        this.view.CurrentChanged += (s, e) => {
            CanExecuteChanged(this, EventArgs.Empty);
        };
    }

    public event EventHandler CanExecuteChanged = delegate { };

    public bool CanExecute(object parameter = null) => view.CanMoveCurrentToNext();

    public void Execute(object parameter = null) {
        if (CanExecute(parameter))
            view.MoveCurrentToNext();
    }
}

MoveCurrentToPreviousCommand

public class MoveCurrentToPreviousCommand : ICommand {
    private readonly ICollectionView view;

    public MoveCurrentToPreviousCommand(ICollectionView view) {
        this.view = view;
        this.view.CurrentChanged += (s, e) => {
            CanExecuteChanged(this, EventArgs.Empty);
        };
    }

    public event EventHandler CanExecuteChanged = delegate { };

    public bool CanExecute(object parameter = null) => view.CanMoveCurrentToPrevious();

    public void Execute(object parameter = null) {
        if (CanExecute(parameter))
            view.MoveCurrentToPrevious();
    }
}

这简化了与视图模型中命令的绑定

public ICommand Next => new MoveCurrentToNextCommand(MyCollectionViewSource.View);
public ICommand Previous => new MoveCurrentToPreviousCommand(MyCollectionViewSource.View);

以下是对命令进行的一些单元测试,以备不时之需。

[TestClass]
public class CollectionViewCommandsTests {
    [TestMethod]
    public void Should_Not_Move_Previous() {
        //Arrange
        var items = new[] { new object(), new object(), new object() };
        var view = new CollectionView(items);
        var expected = view.CurrentItem;
        bool changed = false;
        ICommand command = new MoveCurrentToPreviousCommand(view);
        command.CanExecuteChanged += delegate {
            changed = true;
        };

        //Act
        command.Execute(null);

        //Assert
        var actual = view.CurrentItem;
        actual.Should().Be(expected);
        changed.Should().BeFalse();
    }

    [TestMethod]
    public void Should_Move_Next() {
        //Arrange
        var items = new[] { new object(), new object(), new object() };
        var view = new CollectionView(items);
        var expected = items[1];
        bool changed = false;
        ICommand command = new MoveCurrentToNextCommand(view);
        command.CanExecuteChanged += delegate {
            changed = true;
        };

        //Act
        command.Execute(null);

        //Assert
        var actual = view.CurrentItem;
        actual.Should().Be(expected);
        changed.Should().BeTrue();
    }

    [TestMethod]
    public void Should_Not_Move_Next() {
        //Arrange
        var items = new[] { new object(), new object(), new object() };
        var view = new CollectionView(items);
        view.MoveCurrentToLast();
        var expected = view.CurrentItem;
        bool changed = false;
        ICommand command = new MoveCurrentToNextCommand(view);
        command.CanExecuteChanged += delegate {
            changed = true;
        };

        //Act
        command.Execute(null);

        //Assert
        var actual = view.CurrentItem;
        actual.Should().Be(expected);
        changed.Should().BeFalse();
    }

    [TestMethod]
    public void Should_Move_Previous() {
        //Arrange
        var items = new[] { new object(), new object(), new object() };
        var view = new CollectionView(items);
        view.MoveCurrentToLast();
        var expected = items[1];
        bool changed = false;
        ICommand command = new MoveCurrentToPreviousCommand(view);
        command.CanExecuteChanged += delegate {
            changed = true;
        };

        //Act
        command.Execute(null);

        //Assert
        var actual = view.CurrentItem;
        actual.Should().Be(expected);
        changed.Should().BeTrue();
    }
}