bluebeel bluebeel - 1 month ago 10
C# Question

How to improve my menu (console) in c #

I created a menu with sub menus in console. Everything works fine but I wonder if it is not possible to improve the structure of my program. To simplify it and make it more generic.

class MainClass
{
public static void Main(string[] args)
{
MainMenu();
}

public static void MainMenu()
{
Console.WriteLine("Main Menu");
Console.WriteLine("--------------------");
Console.WriteLine("[1] Show activities");
Console.WriteLine("[2] Show teachers");
Console.WriteLine("[3] Show students");
Console.WriteLine("[4] Exit the program");
Console.WriteLine("--------------------\n");
Console.WriteLine("Please select an option from 1-4\n");

string choice = Console.ReadLine();
int number;
bool result = Int32.TryParse(choice, out number);
if (result)
{
Console.Clear();
SubMenu(number);
}
else
{
Console.WriteLine("Incorrect choice");
}
}

public static void SubMenu(int mainMenuChoice)
{
switch (mainMenuChoice)
{
case 1:
Console.WriteLine("Activities");
Console.WriteLine("[1] Show all activities");
Console.WriteLine("[2] Find a activity by his code");
Console.WriteLine("[3] Return Main Menu");
Console.WriteLine("[4] Exit the program");
Console.WriteLine("--------------------\n");
Console.WriteLine("Please select an option from 1-4\n");
break;

case 2:
Console.WriteLine("Teachers");
Console.WriteLine("[1] Show all teachers");
Console.WriteLine("[2] Find a teacher by his matricule");
Console.WriteLine("[3] Return Main Menu");
Console.WriteLine("[4] Exit the program");
Console.WriteLine("--------------------\n");
Console.WriteLine("Please select an option from 1-4\n");
break;

case 3:
Console.WriteLine("Students");
Console.WriteLine("[1] Show all students");
Console.WriteLine("[2] Find a student by his matricule");
Console.WriteLine("[3] Return Main Menu");
Console.WriteLine("[4] Exit the program");
Console.WriteLine("--------------------\n");
Console.WriteLine("Please select an option from 1-4\n");
break;

case 4:
Environment.Exit(0);
break;
}
string choice = Console.ReadLine();
int number;
bool result = Int32.TryParse(choice, out number);
if (result)
{
Action(mainMenuChoice, number);
}
else
{
Console.WriteLine("Incorrect choice");
}
}
public static void Action(int menu, int choice)
{
switch (menu)
{
case 1:
switch (choice)
{
case 1:
// Do Stuff
break;

case 2:
// Do Stuff
break;

case 3:
Console.Clear();
MainMenu();
break;

case 4:
Environment.Exit(0);
break;
}
break;

case 2:
switch (choice)
{
case 1:
// Do Stuff
break;

case 2:
// Do Stuff
break;

case 3:
Console.Clear();
MainMenu();
break;

case 4:
Environment.Exit(0);
break;
}
break;

case 3:
switch (choice)
{
case 1:
// Do Stuff
break;

case 2:
// Do Stuff
break;

case 3:
Console.Clear();
MainMenu();
break;

case 4:
Environment.Exit(0);
break;
}
break;
}
}
}


Currently if I have to add a sub menu, I have to add a line to the MainMenu() function, I must add a case in the SubMenu() function and as many as there are choices for this menu in Action().
For only one sub menu it's ok but if I have to add a dozen it quickly becomes unmanageable.
I should probably go through one or more classes but I'm lost on the structure.

Answer

I made something quickly to demonstrate one approach to the problem. I commented most of the code, but ask if anything is unclear. The main advantage to me is that you can declare your menus in one place as objects. In my code I have done this explicitly in the Main method, but you could easily write a factory method to generate menus for you.

class Program
{
    //represents a line/option in a menu
    class MenuItem
    {
        // displayed in the menu
        public string Text { get; set; }

        //is there a sub menu
        public bool HasSubMenu { get; set; }

        // if there's a submenu, what's its id
        public int? SubMenuId { get; set; }

        //if there isn't a sub menu, what should we do
        public Action Action { get; set; }
    }

    //represents one menu i.e. a collection of options
    class Menu
    {
        public Menu()
        {
            MenuItems = new List<MenuItem>();
        }

        public int MenuId { get; set; }
        public List<MenuItem> MenuItems { get; set; }
        public string Title { get; set; }

        public void PrintToConsole()
        {
            foreach (MenuItem item in MenuItems)
            {
                Console.WriteLine(MenuItems.IndexOf(item) + " : " + item.Text);
            }
        }
    }

    //represents all the menus
    class MenuCollection
    {
        public MenuCollection()
        {
            Menus = new List<Menu>();
        }

        public List<Menu> Menus { get; set; }

        public void ShowMenu(int id)
        {
            //get the menu we want to display and call its PrintToConsole method
            var currentMenu = Menus.Where(m => m.MenuId == id).Single();
            currentMenu.PrintToConsole();

            //wait for user input
            string choice = Console.ReadLine();
            int choiceIndex;

            //once we have the users selection make sure its an integer and in range of our menu options
            //if not then show an error message and re-display the menu
            if (!int.TryParse(choice, out choiceIndex) || currentMenu.MenuItems.Count < choiceIndex || choiceIndex < 0)
            {
                Console.Clear();
                Console.WriteLine("Invalid selection - try again");
                ShowMenu(id);
            }
            else
            {
                // if the selection is good, then retrieve the corresponding menu item
                var menuItemSelected = currentMenu.MenuItems[choiceIndex];

                // if there's a sub menu then display it
                if (menuItemSelected.HasSubMenu)
                {
                    Console.Clear();
                    ShowMenu(menuItemSelected.SubMenuId.Value);
                }
                // otherwise perform whatever action we need
                else
                {
                    menuItemSelected.Action();
                }
            }
        }
    }

    static void Main(string[] args)
    {
        // build a collection of menus
        // can have as deep a structure as you like
        // give each menu a unique integer MenuId
        // link to other menus by setting HasSubMenu to true, and the SubMenuId to the MenuId of the menu you wish to link to
        // or, set HasSubMenu to false, and have an Action performed when the menuitem is selected
        MenuCollection collection = new MenuCollection()
        {
            Menus =
            {
                new Menu()
                {
                    MenuId = 1,
                    MenuItems =
                    {
                        new MenuItem()
                        {
                            Text = "Go to sub menu",
                            HasSubMenu = true,
                            SubMenuId = 2
                        },
                        new MenuItem()
                        {
                            Text = "Print Action",
                            HasSubMenu = false,
                            Action = () => 
                            {
                                 Console.WriteLine("I printed from an action");
                            }
                        }
                    }
                },
                new Menu()
                {
                    MenuId = 2,
                    MenuItems =
                    {
                        new MenuItem()
                        {
                            Text = "Sub menu option 1",
                            HasSubMenu = false,
                            Action = () => 
                            {
                                Console.WriteLine("Printed from a sub menu");
                            }
                        },
                        new MenuItem()
                        {
                            Text = "Back to the top menu",
                            HasSubMenu = true,
                            SubMenuId = 1
                        }
                    }
                }
            }
        };

        collection.ShowMenu(1);
        Console.ReadLine();
    }
}