Carl
Carl

Reputation: 90

What is the "correct" way of making a console menu?

I've been tasked with writing a program that presents a menu with 4 options to the user, then to take input and do some calculations with the input. What is the best way of doing this?

For reference, my approach is to make an enum with all of the menu options and have it control a switch statement to control what to do.

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

What happens within the cases isn't important but I'd like to know how to make this work syntactically. Since I'm using input from the user, taken by Console.ReadLine() which returns a string and I need it in the form of an enum, I'm lost as to how to proceed. How do I relate sInput with mmChoice and operate the switch?

I know this is sort of asking 2 questions, but for the purposes of the task, I'd prefer someone to answer the specific case and maybe someone can tell me the best general approach in a comment.

Upvotes: 0

Views: 5641

Answers (3)

Rufus L
Rufus L

Reputation: 37020

Here is another approach, which is to create a class that represents a MenuItem, which has a description and an associated method that should be called if that item is chosen.

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

Then we can populate a list of these items, to be shown in a 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},
    };
}

Note that each item has an Execute property, which is of type Action, and they are assigned values above. These values are actually methods that can be defined elsewhere in the project. Each method is currently just a stub that calls a helper method to display a title and another helper method to prompt the user to go back to the main menu, but they could contain real functionality:

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

So now that we have a list of menu items, we can just create a method to show them, which will also get the user input for which item they select, and then call the associated method (via the Execute property).

Note that there is also some validation done here to ensure the user only selects a valid item. We show the items based on their index in the list (we add one to the index so the list doesn't start with 0.), and then we pass the user input to the int.TryParse method. This method will try to parse the user's input into an integer. If the parsing is successful, the method returns true (and our loop exits) and the out parameter will contain the value.

This is done inside a loop, so if the user enters an invalid number, then we set the cursor back to the beginning of the line, write whitespace over it, and then set the cursor back to the beginning again to re-prompt them for input:

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

Now, with that out of the way, it's super easy to customize menu items, show them to the user, and execute the correct methods accordingly:

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

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

Give it a try and see what you think. The menu will look something like this:

enter image description here

Now, what if we want to add a new menu item? Simple! Add this line in the block of code where we assign items to MenuItems inside the PopulateMenuItems method, and run it again:

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

Now our menu looks like:

enter image description here

Upvotes: 1

ats
ats

Reputation: 109

like Rufus L(How should i convert a string to an enum in C#?) already pointed out in the comments: all you have to do is convert your input to an enum using TryParse/Parse respectively

please note that it is advisable to use TryParse when dealing with user-input(or pretty much any other time you cant gurantee that parsing will work).

        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: since OP asked for a char based solution in the comments:

        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
       }

Upvotes: 0

Fabio
Fabio

Reputation: 32445

Just for fun, object-oriented approach

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

Usage will look like this:

vr runner = new JobRunner();

var input = Console.ReadLine();
runner.Run(input);

Upvotes: 1

Related Questions