4
\$\begingroup\$

I was searching for a way to display a List<T> with n elements in a user friendly and easy to handle way within the console application. So I wrote this console-list-pagiation method.

Probably there are ways to improve the usability of my approach.

public static void ConsolePaging<T>(this IEnumerable<T> Values)
{
    int Pagesize = Console.WindowHeight - 2;
    int PageWidth = Console.WindowWidth - 1;
    int CurrentPage = 1;
    int PageCount = (Values.Count() + Pagesize - 1) / Pagesize;
    ConsoleKeyInfo keyInfo = new ConsoleKeyInfo();
    do
    {
        if (keyInfo.Key == ConsoleKey.RightArrow || keyInfo.Key == (ConsoleKey)0 || keyInfo.Key == ConsoleKey.LeftArrow)
        {
            if (keyInfo.Key == ConsoleKey.RightArrow && CurrentPage < PageCount)
            {
                CurrentPage++;
            }
            else if (keyInfo.Key == ConsoleKey.LeftArrow && CurrentPage > 1)
            {
                CurrentPage--;
            }
            Console.Clear();
            Console.BackgroundColor = ConsoleColor.White;
            Console.ForegroundColor = ConsoleColor.DarkRed;
            string HeaderText = string.Format("Page:[{0}]/[{1}] │ Records:[{2}] ║ [◄] Previous │ [►] Next │ [Esc] Escape", CurrentPage, PageCount, Values.Count());
            Console.WriteLine(HeaderText.PadRight(Console.BufferWidth - 1, ' '));
            Console.ResetColor();
            foreach (T item in Values.Skip(Pagesize * (CurrentPage - 1)).Take(Pagesize))
            {
                string row = item.ToString();
                if (row.Length > PageWidth)
                {
                    row = row.Substring(0, PageWidth);
                }
                Console.WriteLine(row);
            }
        }
        keyInfo = Console.ReadKey(true);
    } while (keyInfo.Key != ConsoleKey.Escape);
}

usage:

Enumerable.Range(1, 1000).ToList().ConsolePaging();
\$\endgroup\$

1 Answer 1

7
\$\begingroup\$

To my taste it's doing way to much. You'd be better off if you implemented the paging logic for a collection without mixing it with console interaction.

Write another method that expects a collection, page, page length etc. and do the navigation in another method.

If you do this you can test the paging alogorithm without the console and without key input. You'll be also able to test the navigation separately.

Even better, your paging method will be reusable.


To speed up the paging you could group the items by page. If you then ToList() it you can access each page directly without reiterating the collection each time:

public static IEnumerable<IGrouping<int, T>> GroupByPage<T>(this IEnumerable<T> values, int pageSize)
{
    var currentPage = 0;
    var page = new Func<int, int>(index => index > 0 && index % pageSize == 0 ? ++currentPage : currentPage);
    return values
        .Select((v, i) => new { v, i })
        .GroupBy(x => page(x.i), x => x.v)
        .ToList();
}

Here's another example with a decorator:

First we create an interface:

public interface IPager<out T>
{
    int PageIndex { get; }
    int PageSize { get; }
    int PageCount { get; }
    IGrouping<int, T> CurrentPage { get; }
    IGrouping<int, T> this[int page] { get; }
    void Next();
    void Prev();
}

Then we create a general pager:

public class Pager<T> : IPager<T>
{
    private int _pageIndex;

    private readonly IEnumerable<IGrouping<int, T>> _pages;

    public Pager(IEnumerable<T> values, int pageSize)
    {
        PageSize = pageSize;
        var currentPage = 0;
        var page = new Func<int, int>(index => index > 0 && index % pageSize == 0 ? ++currentPage : currentPage);
        _pages = values
            .Select((v, i) => new { v, i })
            .GroupBy(x => page(x.i), x => x.v);
    }

    public int PageIndex => _pageIndex;

    public int PageSize { get; }

    public int PageCount => _pages.Count();

    public IGrouping<int, T> this[int page] => _pages.ElementAtOrDefault(page);

    public IGrouping<int, T> CurrentPage => this[_pageIndex];

    public void Next() => _pageIndex++;

    public void Prev() => _pageIndex--;

}

Now we create another pager but this time it can display the item in the console:

public class ConsolePager<T> : IPager<T>
{
    private readonly IPager<T> _pager;

    public ConsolePager(IPager<T> pager)
    {
        _pager = pager;
    }

    public int PageIndex => _pager.PageIndex;

    public int PageSize => _pager.PageSize;

    public int PageCount => _pager.PageCount;

    public IGrouping<int, T> CurrentPage => _pager.CurrentPage;

    public void Next() => _pager.Next();

    public void Prev() => _pager.Prev();

    public IGrouping<int, T> this[int page] => _pager[page];

    public void Run()
    {
        var pageWidth = Console.WindowWidth - 1;
        var keyInfo = new ConsoleKeyInfo();

        do
        {
            if (keyInfo.Key == ConsoleKey.RightArrow || keyInfo.Key == (ConsoleKey)0 || keyInfo.Key == ConsoleKey.LeftArrow)
            {
                if (keyInfo.Key == ConsoleKey.RightArrow && _pager.PageIndex < _pager.PageCount - 1)
                {
                    _pager.Next();
                }
                else if (keyInfo.Key == ConsoleKey.LeftArrow && _pager.PageIndex > 1)
                {
                    _pager.Prev();
                }
                Console.Clear();
                Console.BackgroundColor = ConsoleColor.White;
                Console.ForegroundColor = ConsoleColor.DarkRed;
                var headerText = string.Format("Page:[{0}]/[{1}] │ Records:[{2}] ║ [◄] Previous │ [►] Next │ [Esc] Escape", _pager.PageIndex + 1, _pager.PageCount, _pager.CurrentPage.Count());
                Console.WriteLine(headerText.PadRight(Console.BufferWidth - 1, ' '));
                Console.ResetColor();

                foreach (var item in _pager.CurrentPage)
                {
                    var row = item.ToString();
                    if (row.Length > pageWidth)
                    {
                        row = row.Substring(0, pageWidth);
                    }
                    Console.WriteLine(row);
                }
            }
            keyInfo = Console.ReadKey(true);
        } while (keyInfo.Key != ConsoleKey.Escape);
    }
}

Usage:

new ConsolePager<int>(
    new Pager<int>(Enumerable.Range(1, 1000), 
    Console.WindowHeight - 2)
).Run();
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.