For today I'd prepared short tutorial and demo on how to configure and use ConsoleMenu component.

Demo source is located here: MenuDemos.cs
Menu is placed inside single yellow frame (frame-box) rendered separately:
var aConsole = new AtomicConsole(console); int menuStartY = 6; int menuStartX = 5; int menuContentWidth = 25; var frameColors = new ConsoleFontColor(Color.Yellow, Color.Black); var boxInnerColors = new ConsoleFontColor(Color.WhiteSmoke, Color.Black); var frameStyle = new FrameStyle(frameColors, boxInnerColors, @"┌─┐││└─┘", ' '); var ops = new ConsoleOperations(aConsole); ops.WriteTextBox(new Rectangle(menuStartX - 1, menuStartY - 1, menuContentWidth + 2, menuItems.Length + 2), "", frameStyle);
Next step is to define menu items - these objects are very simple, contain only Caption, Enabled flag and unique identifier:
var exitGuid = new Guid(@"a7725515-7f82-4c18-9c36-343003bdf20d"); var menuItems = new ConsoleMenuItem[] { new ConsoleMenuItem { Enabled = true, Caption = "menu item number one", Code = Guid.NewGuid() }, // ... new ConsoleMenuItem { Enabled = true, Caption = "menu item number four with very long description", Code = Guid.NewGuid() }, // ... new ConsoleMenuItem { Enabled = true, Caption = "Exit menu DEMO", Code = exitGuid } };
Next step is to configure menu styling - mostly alignment and colors:

One can also redefine other properties, like navigation keys (default are UP/DOWN ARROWS, HOME, END, ENTER), or whether ellipsis should be added on overflowing captions.
Then one can create menu-control and let it render statically:
var menu = new ConsoleMenu(aConsole, new Rectangle(menuStartX, menuStartY, menuContentWidth, 0), menuItems, menuStyling); menu.RenderAll();
And now we can finally execute the menu:
ConsoleMenuItem result = null; while (result == null || result.Code != exitGuid) { result = menu.Focus(resetActiveItem: true); aConsole.CleanLineSync(0, Color.Black); aConsole.RunAtomicOperations(ac => { aConsole.SetCursorPosition(0, 0); aConsole.PrintColorfullText( new KeyValuePair<ConsoleFontColor, string>(new ConsoleFontColor(Color.BlanchedAlmond, Color.Black), @"Selected menu: "), new KeyValuePair<ConsoleFontColor, string>(new ConsoleFontColor(Color.Gold, Color.Black), result.Caption) ); }); Thread.Sleep(1000); } aConsole.SetCursorPosition(0, menuStartY + menuItems.Length + 1); aConsole.ShowCursor();
There are several things to be noticed here:
Sleep is used for demo purposes so one can notice activated item, before menu is focused again and displays selection bar, removing Activated Item formatting.
ShowCursor is used to restore cursor. By design Focus() function always hides cursor so it does not render as strange artifact when menu is activated. Therefore it's up to developer to display it when necessary.
RunAtomicOperations is used to make sure, that no other operation on console breaks desired sequence of operations, that shall be done uninterrupted, like positioning cursor, changing colors and writing text pieces. This is utmost important when implementing live interfaces, where pieces of screen are being controlled asynchronously. This is also main design reason, why most operations provide atomic overloads anyway. It's highly recommended to use AtomicConsole wrapper over normal IConsole whenever any more complicated controls or scenarios than simple printing are being performed. Some of the defined helpers and extensions even require that interface (Note, however, that this part of the libraries is under heavy scrutiny so might be changed in the future if any deadlocks are detected.).
No comments:
Post a Comment