HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Display a List<T> in Console

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
consolelistdisplay

Problem

I was searching for a way to display a List 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(this IEnumerable 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  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();

Solution

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> GroupByPage(this IEnumerable values, int pageSize)
{
    var currentPage = 0;
    var page = new Func(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
{
    int PageIndex { get; }
    int PageSize { get; }
    int PageCount { get; }
    IGrouping CurrentPage { get; }
    IGrouping this[int page] { get; }
    void Next();
    void Prev();
}


Then we create a general pager:

public class Pager : IPager
{
    private int _pageIndex;

    private readonly IEnumerable> _pages;

    public Pager(IEnumerable values, int pageSize)
    {
        PageSize = pageSize;
        var currentPage = 0;
        var page = new Func(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 this[int page] => _pages.ElementAtOrDefault(page);

    public IGrouping 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 : IPager
{
    private readonly IPager _pager;

    public ConsolePager(IPager pager)
    {
        _pager = pager;
    }

    public int PageIndex => _pager.PageIndex;

    public int PageSize => _pager.PageSize;

    public int PageCount => _pager.PageCount;

    public IGrouping CurrentPage => _pager.CurrentPage;

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

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

    public IGrouping 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  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(
    new Pager(Enumerable.Range(1, 1000), 
    Console.WindowHeight - 2)
).Run();

Code Snippets

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

}
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);
    }
}
new ConsolePager<int>(
    new Pager<int>(Enumerable.Range(1, 1000), 
    Console.WindowHeight - 2)
).Run();

Context

StackExchange Code Review Q#139679, answer score: 7

Revisions (0)

No revisions yet.