Каков «правильный» способ составления консольного меню? - PullRequest
0 голосов
/ 10 ноября 2018

Мне было поручено написать программу, которая представляет пользователю меню с 4 опциями, а затем принять ввод и выполнить некоторые вычисления с вводом. Каков наилучший способ сделать это?

Для справки, мой подход - создать enum со всеми опциями меню и заставить его управлять оператором switch, чтобы контролировать, что делать.

    private enum MainMenu
    {
        CYLINDER = 1,
        CUBE,
        SPHERE,
        QUIT,
        UNASSIGNED
    }
    MainMenu mmChoice = MainMenu.UNASSIGNED;
    string sInput = Console.ReadLine();
    switch (mmChoice)
    {
        case MainMenu.CYLINDER:
            doWork1();
            break;
        case MainMenu.CUBE:
            doWork2();
            break;
        case MainMenu.SPHERE:
            doWork3();
            break;
        case MainMenu.QUIT:
            Exit();
            break;
        case MainMenu.UNASSIGNED:
            break;
    }

То, что происходит внутри случаев, не важно, но я хотел бы знать, как сделать это синтаксически. Так как я использую ввод от пользователя, взятый Console.ReadLine(), который возвращает string, и мне нужно это в форме enum, я заблудился относительно того, как действовать. Как связать sInput с mmChoice и управлять коммутатором?

Я знаю, что это своего рода задание 2 вопросов, но для целей задачи я бы предпочел, чтобы кто-то ответил на конкретный случай, и, возможно, кто-то может сказать мне лучший общий подход в комментарии.

Ответы [ 3 ]

0 голосов
/ 10 ноября 2018

Просто для удовольствия, объектно-ориентированный подход

public interface IJob
{
    void Do();
}

public class JobOne : IJob
{
    public void Do()
    {
        // do something
    }
}

public class JobTwo : IJob
{
    public void Do()
    {
        // do something different
    }
}

public class DoNothing : IJob
{
    public void Do() { }
}

public class JobRunner
{
    private readonly Dictionary<string IJob> _jobs;
    private readonly IJob _doNothing;

    public JobRunner()
    {
        _jobs = new Dictionary<string, IJob>
        {
            { "one", new JobOne() },
            { "two", new JobTwo() },
        };
        _doNothing = new DoNothing();
    }

    public void Run(string jobKey)
    {
        _jobs.GetValueOrDefault(jobkey, _doNothing).Do();
    }
}

Использование будет выглядеть так:

vr runner = new JobRunner();

var input = Console.ReadLine();
runner.Run(input);
0 голосов
/ 10 ноября 2018

Вот еще один подход, который заключается в создании класса, представляющего MenuItem, который имеет описание и связанный метод, который следует вызывать, если выбран этот элемент.

private class MenuItem
{
    public string Description { get; set; }
    public Action Execute { get; set; }
}

Затем мы можем заполнить список этих предметов, которые будут показаны в MainMenu

// Our private list of menu items that will be displayed to the user
private static List<MenuItem> MenuItems;

// A helper method to populate our list. 
// We can easily add or remove new items to the menu
// without having to worry about changing an 
// enum or adding a command to a switch block
private static void PopulateMenuItems()
{
    MenuItems = new List<MenuItem>
    {
        new MenuItem {Description = "View balance", Execute = ViewBalance},
        new MenuItem {Description = "Deposit", Execute = Deposit},
        new MenuItem {Description = "Withdraw", Execute = Withdraw},
    };
}

Обратите внимание, что у каждого элемента есть свойство Execute, которое имеет тип Action, и им назначены значения выше. Эти значения на самом деле являются методами, которые могут быть определены в другом месте в проекте. Каждый метод в настоящее время является просто заглушкой, которая вызывает вспомогательный метод для отображения заголовка и другой вспомогательный метод, чтобы предложить пользователю вернуться в главное меню, но они могут содержать реальную функциональность:

private static void ViewBalance()
{
    ClearAndShowHeading("Account Balance");
    GoToMainMenu();
}

private static void Deposit()
{
    ClearAndShowHeading("Account Deposit");
    GoToMainMenu();
}

private static void Withdraw()
{
    ClearAndShowHeading("Account Withdrawl");
    GoToMainMenu();
}

private static void ClearAndShowHeading(string heading)
{
    Console.Clear();
    Console.WriteLine(heading);
    Console.WriteLine(new string('-', heading?.Length ?? 0));
}

private static void GoToMainMenu()
{
    Console.Write("\nPress any key to go to the main menu...");
    Console.ReadKey();
    MainMenu();
}

Итак, теперь у нас есть список пунктов меню, мы можем просто создать метод для их отображения, который также получит пользовательский ввод для выбранного элемента, а затем вызовет связанный метод (через свойство Execute ).

Обратите внимание, что здесь также выполняется некоторая проверка, чтобы гарантировать, что пользователь только выбирает действительный элемент. Мы показываем элементы на основе их индекса в списке (мы добавляем один к индексу, чтобы список не начинался с 0.), а затем мы передаем пользовательский ввод методу int.TryParse. Этот метод попытается проанализировать ввод пользователя в целое число. Если синтаксический анализ успешен, метод возвращает true (и наш цикл завершается), а параметр out будет содержать значение.

Это делается внутри цикла, поэтому, если пользователь вводит недопустимое число, то мы возвращаем курсор к началу строки, записываем пробел над ней, а затем снова устанавливаем курсор на начало, чтобы запросить их для ввода:

static void MainMenu()
{
    ClearAndShowHeading("Main Menu");

    // Write out the menu options
    for (int i = 0; i < MenuItems.Count; i++)
    {
        Console.WriteLine($"  {i + 1}. {MenuItems[i].Description}");
    }

    // Get the cursor position for later use 
    // (to clear the line if they enter invalid input)
    int cursorTop = Console.CursorTop + 1;
    int userInput;

    // Get the user input
    do
    {
        // These three lines clear the previous input so we can re-display the prompt
        Console.SetCursorPosition(0, cursorTop);
        Console.Write(new string(' ', Console.WindowWidth));
        Console.SetCursorPosition(0, cursorTop);

        Console.Write($"Enter a choice (1 - {MenuItems.Count}): ");
    } while (!int.TryParse(Console.ReadLine(), out userInput) ||
             userInput < 1 || userInput > MenuItems.Count);

    // Execute the menu item function
    MenuItems[userInput - 1].Execute();
}

Теперь, благодаря этому, очень легко настроить элементы меню, показать их пользователю и выполнить соответствующие методы соответствующим образом:

static void Main(string[] args)
{
    PopulateMenuItems();
    MainMenu();

    Console.Write("\nDone! Press any key to exit...");
    Console.ReadKey();
}

Попробуйте и посмотрите, что вы думаете. Меню будет выглядеть примерно так:

enter image description here

А что если мы хотим добавить новый пункт меню? Просто! Добавьте эту строку в блок кода, где мы присваиваем элементы MenuItems внутри метода PopulateMenuItems, и запустите ее снова:

new MenuItem {Description = "Exit", Execute = Application.Exit}

Теперь наше меню выглядит так:

enter image description here

0 голосов
/ 10 ноября 2018

как Rufus L ( Как мне преобразовать строку в перечисление в C #? ), уже указанное в комментариях: все, что вам нужно сделать, это преобразовать ваш ввод в перечисление, используя TryParse / Parse соответственно

обратите внимание, что желательно использовать TryParse при работе с пользовательским вводом (или практически в любой другой раз, когда вы не можете гарантировать, что синтаксический анализ будет работать).

        MainMenu mmChoice = MainMenu.UNASSIGNED;
        Console.WriteLine("?");
        string sInput = Console.ReadLine();
        if(Enum.TryParse(sInput, out mmChoice))
        {
            switch(mmChoice)
            {
                case MainMenu.CYLINDER:
                    Console.WriteLine("cylinder");
                    break;
                case MainMenu.CUBE:
                    Console.WriteLine("cube");
                    break;
                case MainMenu.SPHERE:
                    Console.WriteLine("sphere");
                    break;
                case MainMenu.QUIT:
                    Console.WriteLine("quit");
                    break;
                case MainMenu.UNASSIGNED:
                    break;
            }
        }

EDIT : так как ОП попросил решение на основе символов в комментариях:

        Console.WriteLine("?");
        char sInput = Console.ReadKey().KeyChar;
        Console.WriteLine("");
        switch (sInput)
        {
            case 'a':
                Console.WriteLine("cylinder");
                break;
            case 'b':
                Console.WriteLine("cube");
                break;
            case 'c':
                Console.WriteLine("sphere");
                break;
            case 'd':
                Console.WriteLine("quit");
                break;
            default :
                break; // error handling here
       }
...