Skip to content

Instantly share code, notes, and snippets.

@dbones
Created September 5, 2013 23:24
Show Gist options
  • Save dbones/6457597 to your computer and use it in GitHub Desktop.
Save dbones/6457597 to your computer and use it in GitHub Desktop.
wondering how you may implement a menu (navigation)
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