Joe
Joe

Reputation: 1326

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

I am reading rows from an MS SQL Server table into a C# CollectionViewSource via Entity Framework. One row = one collection entry.

I use data binding to connect each CollectionViewSource entry's data elements to a WPF GUIs' controls. The user uses buttons on the GUI to page backwards and forwards through the collection entries using command handlers like the one below.

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

All worked well until I started including "SelectionChanged" and "TextChanged" events in my GUI ComboBox and Text controls. These events fire when I move to the next or previous entry in the collection. Everything works until I get to the first or last entries in the collection.

The "IsCurrentAfterLast" test doesn't stop me from paging past the last entry in the collection and when I do I get an "Object reference not set to an instance of an object" exception. I'm assuming that the exception is caused when the "SelectionChanged" and "TextChanged" events encounter spurious data before the first or after the last collection entries.

In the absence of something slick like "IsCurrentFirst" and "IsCurrentLast" can anyone suggest an efficient way to count the entries in the collection so that I can avoid moving past the first and last ones?

Upvotes: 2

Views: 702

Answers (1)

Nkosi
Nkosi

Reputation: 247088

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

Simple enough to create some extension methods on the ICollectionView abstraction to provide the desired functionality

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

The extension methods should now allow for such checks.

Creating some derived ICommand implementations that can be hooked directly to previous and next buttons.

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

This simplifies binding with commands in a view model

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

Here are some unit tests on the commands for good measure.

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

Upvotes: 6

Related Questions