Instantly share code, notes, and snippets.
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save dbones/6457597 to your computer and use it in GitHub Desktop.
wondering how you may implement a menu (navigation)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
namespace TestMenuIdea | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
//the menu providers are to be located in different modules | |
var mips = new List<IMenuItemProvider> {new menuItemProvider1(), new menuItemProvider2()}; | |
IMenu menu= new Menu(mips, new MenuItemWriteProcessOrder()); | |
menu.Print(new ConsoleMenuWriter()); | |
Console.ReadKey(); | |
} | |
} | |
public class menuItemProvider1 : IMenuItemProvider | |
{ | |
public IEnumerable<MenuItem> MenuItems | |
{ | |
get | |
{ | |
return new List<MenuItem>() | |
{ | |
new MenuItem("m1", "File"), | |
new MenuItem("m2", "Settings"), | |
new MenuItem("m3", "Open", "m1"), | |
}; | |
} | |
} | |
} | |
public class menuItemProvider2 : IMenuItemProvider | |
{ | |
public IEnumerable<MenuItem> MenuItems | |
{ | |
get | |
{ | |
return new List<MenuItem>() | |
{ | |
new MenuItem("m9", "Solution", new FuncParentPattern( | |
(item, menus) => | |
{ | |
if (item.Name.ToLower() != "open" && item.ParentId == null) return false; | |
return menus.ById(item.ParentId).Name.ToLower() == "file"; | |
})), | |
new MenuItem("m8", "Project", new FuncParentPattern( | |
(item, menus) => | |
{ | |
if (item.Name.ToLower() != "open" && item.ParentId == null) return false; | |
return menus.ById(item.ParentId).Name.ToLower() == "file"; | |
})), | |
new MenuItem("m4", "Close", new FuncParentPattern((item, menus) => item.Name.ToLower() == "file")), | |
new MenuItem("m6", "Help"), | |
new MenuItem("m5", "About Us", "m6"), | |
}; | |
} | |
} | |
} | |
public interface IMenu | |
{ | |
MenuItem ById(string id); | |
IEnumerable<MenuItem> Query(Func<MenuItem, bool> where); | |
void AddMenuProvider(IMenuItemProvider menuItemProvider); | |
void Print(IMenuWriter writer); | |
} | |
public class Menu : IMenu | |
{ | |
private readonly IMenuItemWriteProcessOrder _menuItemWriteProcessOrder; | |
private readonly Dictionary<string, MenuItem> _menuItems; | |
private bool _requiresProcessing; | |
IEnumerable<MenuItem> _ordered; | |
public Menu(IEnumerable<IMenuItemProvider> menuItemProviders, IMenuItemWriteProcessOrder menuItemWriteProcessOrder) | |
{ | |
_menuItemWriteProcessOrder = menuItemWriteProcessOrder; | |
IEnumerable<MenuItem> menuItems = menuItemProviders.SelectMany(mi => mi.MenuItems); | |
_menuItems = new Dictionary<string, MenuItem>(); | |
foreach (var menuItem in menuItems) | |
{ | |
_menuItems.Add(menuItem.Id, menuItem); | |
} | |
_requiresProcessing = true; | |
} | |
public MenuItem ById(string id) | |
{ | |
MenuItem item; | |
return _menuItems.TryGetValue(id, out item) ? item : null; | |
} | |
public IEnumerable<MenuItem> Query(Func<MenuItem, bool> @where) | |
{ | |
return _menuItems.Values.Where(@where); | |
} | |
public void AddMenuProvider(IMenuItemProvider menuItemProvider) | |
{ | |
foreach (var menuItem in menuItemProvider.MenuItems) | |
{ | |
_menuItems.Add(menuItem.Id, menuItem); | |
} | |
_requiresProcessing = true; | |
} | |
public void Print(IMenuWriter writer) | |
{ | |
if (_requiresProcessing) | |
{ | |
ProcessMenuItems(); | |
_requiresProcessing = false; | |
} | |
_menuItemWriteProcessOrder.Process(_menuItems.Values, writer, new WriteMenuItemNode()); | |
} | |
/// <summary> | |
/// ensure all menu items have their parent id set. | |
/// </summary> | |
private void ProcessMenuItems() | |
{ | |
foreach (var menuItem in _menuItems.Values.Where(item => item.ParentId == null && item.Pattern != null)) | |
{ | |
var parent = _menuItems.Values | |
.Where(x => menuItem != x) | |
.SingleOrDefault(possibleParent => menuItem.Pattern.Match(possibleParent, this)); | |
if (parent != null) | |
{ | |
menuItem.ParentId = parent.Id; | |
} | |
} | |
} | |
} | |
public interface IMenuItemWriteProcessOrder | |
{ | |
void Process(IEnumerable<MenuItem> items, IMenuWriter writer, IWriteMenuItemNode writeMenuItemNode); | |
} | |
public class MenuItemWriteProcessOrder : IMenuItemWriteProcessOrder | |
{ | |
readonly IDictionary<MenuItem, Node<MenuItem>> _itemsToNodes = new Dictionary<MenuItem, Node<MenuItem>>(); | |
readonly IDictionary<string, Node<MenuItem>> _idsToNodes = new Dictionary<string, Node<MenuItem>>(); | |
public void Process(IEnumerable<MenuItem> items, IMenuWriter writer, IWriteMenuItemNode writeMenuItemNode) | |
{ | |
var menuItems = items as IList<MenuItem> ?? items.ToList(); | |
int count = _itemsToNodes.Count; | |
foreach (var menuItem in menuItems) | |
{ | |
if (_itemsToNodes.ContainsKey(menuItem)) | |
{ | |
continue; | |
} | |
var node = new Node<MenuItem>(menuItem); | |
_itemsToNodes.Add(menuItem, node); | |
_idsToNodes.Add(menuItem.Id, node); | |
} | |
if (count != _itemsToNodes.Count) | |
{ | |
foreach (var menuItem in menuItems) | |
{ | |
var node = _itemsToNodes[menuItem]; | |
if (node.Element.ParentId == null) | |
{ | |
continue; | |
} | |
var parentNode = _idsToNodes[menuItem.ParentId]; | |
parentNode.Dependencies.Add(node); | |
} | |
} | |
var enumerable = menuItems.Select(x => _itemsToNodes[x]).ToList(); | |
enumerable.ForEach(n => n.Visited = false); | |
enumerable.ForEach(n => writeMenuItemNode.Visit(n, writer)); | |
} | |
} | |
public interface IMenuWriter | |
{ | |
void AppendMenuItem(MenuItem item); | |
void StartChildren(); | |
void EndChildren(); | |
} | |
public class ConsoleMenuWriter : IMenuWriter | |
{ | |
private int _currentIndent = 0; | |
public void AppendMenuItem(MenuItem item) | |
{ | |
/* | |
* To do the following we would need to know of | |
* the entire structure first | |
* /--+- menu 1 -+- menu 31 | |
* | |- menu 99 | |
* | \- menu 123 | |
* \- Epic menu 2 -+- menu 12 | |
* \- menu 2334 | |
* for now | |
* menu 1 | |
* menu 33 | |
* menu 234 | |
* menu 3 | |
* menu 123 | |
* | |
* | |
* */ | |
Console.WriteLine(item.Name.AddPadLeft(_currentIndent, ' ')); | |
} | |
public void StartChildren() | |
{ | |
_currentIndent += 4; | |
} | |
public void EndChildren() | |
{ | |
_currentIndent -= 4; | |
} | |
} | |
public static class StringExtensions | |
{ | |
public static string AddPadLeft(this string str, int pad, char padWith) | |
{ | |
var sb = new StringBuilder(); | |
for (int i = 0; i < pad; i++) | |
{ | |
sb.Append(padWith); | |
} | |
sb.Append(str); | |
return sb.ToString(); | |
} | |
} | |
public interface IMenuItemProvider | |
{ | |
IEnumerable<MenuItem> MenuItems { get; } | |
} | |
public class MenuItem | |
{ | |
private readonly int _hash; | |
public MenuItem(string id, string name, string parentId) | |
: this(id, name) | |
{ | |
ParentId = parentId; | |
} | |
public MenuItem(string id, string name, IParentPattern pattern) | |
: this(id, name) | |
{ | |
Pattern = pattern; | |
} | |
public MenuItem(string id, string name) | |
{ | |
Id = id; | |
Name = name; | |
_hash = id.GetHashCode(); | |
} | |
public string Id { get; private set; } | |
public string Name { get; private set; } | |
public string ParentId { get; internal set; } | |
public IParentPattern Pattern { get; private set; } | |
public override bool Equals(object obj) | |
{ | |
var item = obj as MenuItem; | |
if (item == null) | |
{ | |
return false; | |
} | |
return _hash == item.GetHashCode(); | |
} | |
public override int GetHashCode() | |
{ | |
return _hash; | |
} | |
} | |
/// <summary> | |
/// find the parent menu item via a pattern | |
/// </summary> | |
public interface IParentPattern | |
{ | |
/// <summary> | |
/// this will indicate if the possibleMenuItem is a matcg for the current menu item | |
/// </summary> | |
/// <param name="possibleParent">a possible parent menu item</param> | |
/// <param name="menu">the menu, used to search the current menu's for more information</param> | |
/// <returns>true if the possibleParent is the menu items parent</returns> | |
bool Match(MenuItem possibleParent, IMenu menu); | |
} | |
class FuncParentPattern : IParentPattern | |
{ | |
private readonly Func<MenuItem, IMenu, bool> _pattern; | |
public FuncParentPattern(Func<MenuItem, IMenu, bool> pattern) | |
{ | |
_pattern = pattern; | |
} | |
public bool Match(MenuItem possibleParent, IMenu menu) | |
{ | |
return _pattern(possibleParent, menu); | |
} | |
} | |
public interface IWriteMenuItemNode | |
{ | |
void Visit(Node<MenuItem> n, IMenuWriter writer); | |
} | |
public class WriteMenuItemNode : IWriteMenuItemNode | |
{ | |
public void Visit(Node<MenuItem> n, IMenuWriter writer) | |
{ | |
if (n.Visited) | |
{ | |
return; | |
} | |
n.Visited = true; | |
writer.AppendMenuItem(n.Element); | |
if (n.Dependencies.Any()) | |
{ | |
writer.StartChildren(); | |
foreach (Node<MenuItem> dependency in n.Dependencies) | |
{ | |
Visit(dependency, writer); | |
} | |
writer.EndChildren(); | |
} | |
} | |
} | |
/// <summary> | |
/// These nodes are used to implement a dependency sort | |
/// </summary> | |
/// <typeparam name="T">the item associated with the node</typeparam> | |
public class Node<T> | |
{ | |
/// <summary> | |
/// The instance this node represents | |
/// </summary> | |
public T Element { get; private set; } | |
/// <summary> | |
/// Indicates if the node has been visited/processed | |
/// </summary> | |
public bool Visited { get; set; } | |
/// <summary> | |
/// dependencies of this node | |
/// </summary> | |
public List<Node<T>> Dependencies { get; private set; } | |
public Node(T element) | |
{ | |
Element = element; | |
Dependencies = new List<Node<T>>(); | |
} | |
} | |
public static class EnumberableExtensions | |
{ | |
/// <summary> | |
/// for each | |
/// </summary> | |
/// <typeparam name="T">the type in the collection</typeparam> | |
/// <param name="collection">the collection</param> | |
/// <param name="action">action to perform on each item</param> | |
public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action) | |
{ | |
foreach (var item in collection) | |
{ | |
action(item); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment