Reputation: 128
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.
Upvotes: 1
Views: 3843
Reputation: 4039
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();
}
}
Upvotes: 3
Reputation: 128
I have been making a lot of small CLI tools recently, I only fleshed out Activities, but here's an example of the approach I generally like to take for lots of branching menus/logic:
class Program
{
static T GetChoice<T>(List<Tuple<string, T>> choices)
{
var i = 1;
choices.ForEach(x => Console.WriteLine($"[{i++}]: {x.Item1}"));
var choiceIndex = int.Parse(Console.ReadLine());
return choices[choiceIndex - 1].Item2;
}
static void Main(string[] args)
{
Console.WriteLine("Main Menu: ");
var choices = new List<Tuple<string, Action>>()
{
new Tuple<string, Action>("Show Activities", ShowActivities),
new Tuple<string, Action>("Show Teachers", ShowTeachers),
new Tuple<string, Action>("Show Students", ShowStudents),
new Tuple<string, Action>("Exit", Exit),
};
GetChoice(choices)();
}
static void ShowActivities()
{
Console.WriteLine("Activities: ");
var choices = new List<Tuple<string, Action>>()
{
new Tuple<string, Action>("Show all activities", ShowAllActivities),
new Tuple<string, Action>("Find activity by code", FindActivityByCode),
new Tuple<string, Action>("Return to main menu", () => { Main(null); }),
new Tuple<string, Action>("Exit the program", Exit),
};
GetChoice(choices)();
}
static void ShowTeachers()
{
Console.WriteLine("Teachers: ");
var choices = new List<Tuple<string, Action>>();
}
static void ShowStudents()
{
Console.WriteLine("Students: ");
var choices = new List<Tuple<string, Action>>();
}
static void ShowAllActivities()
{
//Do stuff
}
static void FindActivityByCode()
{
//Do stuff
}
static void ReturnToMainMenu()
{
//Do stuff
}
static void Exit()
{
Environment.Exit(0);
}
}
Upvotes: 0