Category Archives: console-framework

Introduction

There are many APIs for creating GUI applications. They progress in development, staying more convinient, simplier, better. But nobody cares about old good text user interface ! Though it is very simple way to create application that can run on all platforms, including launching in terminal emulator connected via SSH. Imho, it is omission.When I was little and was studied at school, I had the Pentium I 133MHz computer. I used many TUI programs and I was impressed of how they were look like. It was RAR archiver, Norton Commander, FAR manager. But when I wanted to write something like it, I was dissappointed in tools that were available.

And I have decided to write my own library to provide the easiest and most efficient way to implement simple TUI applications using modern technologies.

Technical overview

The library is written in C#, it can be built in Windows, Linux and Mac OS X. In Windows there is no need in additional libraries because standard Win32 console API is enough. In Linux and Mac next additional libraries are used: libc, libtermkey and ncurses.

Native dependencies in Linux and Mac OS X

Libc is standard library, it is included in all Linux and Mac distros. It is used for polling (POSIX equivalent for WaitForMultipleObjects) standard input (STDIN) and self-pipe. Self-pipe is necessary to interrupt waiting for input event when need to execute some code in UI thread asynchronously.

NCurses is used for rendering symbols on the screen. Actually, it is not necessary to use this library, but I have decided to use this one to avoid troubles with different terminals correct handling. And using this library allowed me to port my code from Win32 to Linux/Mac quickly. In future, may be, ncurses will be removed from required dependencies.

Libtermkey is awesome library written by Paul Evans. It allows to handle keyboard and mouse input and don’t worry about various terminals. It has elegant C API.

Main objects

Central object of any program is ConsoleApplication. It is singleton object and is accessible via ConsoleApplication.Instance property. When you call ConsoleApplication.Run(control), it launches the event loop, which is stopped only after ConsoleApplication.Stop() method has been called.

You can pass any Control into Run() method. But usually, if you want to create windows-based program, you should pass WindowsHost instance there. WindowsHost is Control that manages the children: windows and single main menu.

Control passed to Run() method becomes a Root Element. Console application has only one Root Element and it cannot be changed before Stop() is called.

EventManager is responsible to deliver rounted events to their subscribers. Routed events work similar to WPF’s. They can be bubbling, tunneling or direct. Bubbling events are propagated from source control up to root of controls tree (Root Element), tunneling events are propagated from root to source. Direct events are not propagated in controls tree. Event Manager is single in application.

FocusManager tracks a keyboard focus. It is single object too, like Event Manager, and provides access to API for manipulating keyboard focus. Controls are allowed to call FocusManager.SetFocusScope() method or FocusManager.SetFocus() to pass keyboard focus to specified control.

Layout system

Layout system is similar with Windows Presentation Foundation. But in this library there are no separation on ContentControls and ItemsControls. Any Control can have a one child or a multiple children. But algorithms of measuring and arrangement are quite identical to WPF. If you want to write your custom control, you should read about WPF Measuring and Arranging children. Differences from WPF in layout:

No ContentControls and ItemsControls.

No templates: there is no need to do this. It is complex, and console controls don’t have many pixels to allow template composition.

Rendering system

Rendering is implemented in Renderer class. It is called on every event loop iteration and updates all invalidated controls. If you have called Invalidate() for some control, it will refresh its rendering buffer and will flush it to screen anyway. If you have called Invalidate() on some child control, and this control has been remeasured to the same size, parent control will be not affected in invalidation process. Invalidation status propagates to parent if remeasured child’s desired size differs from previous calculation.

After processing of invalidated controls updated controls are flushed their rendering buffers to PhysicalCanvas instance. Finally, PhysicalCancas flushes its buffer to the terminal screen.

XAML and Data Binding

XAML support is implemented using custom parser. This choice is done to avoid troubles with standard .NET XAML API and avoid dependencies from WPF parts of .NET class library. Differences from WPF in XAML are:

No Attached Properties (mb will be added later)

No generated code: whole markup is parsed in run-time

No x:Name attribute, instead – x:Id

No includes support (will be added later)

May be some differences in handling values conversion, adding to collections (because this XAML handling method differs from WPF’s code generation scheme)

Some differences in markup extension syntax – no unescaped symbols in single quotes allowed, for example.

Data Context object does not inherit from parent controls, it is passed as argument and remains actual to whole object configured using specified XAML.

This example shows how to create Grid-based markup and how to place controls in it. Markup is very similar to WPF’s XAML. As you can see, it is not so big markup (and can be optimized) for this complex layout.

Participating in the project

Library is in active development now, so help in writing additional controls and documentation is greatly appreciated. Next controls are missing yet: tab panel, text editor, context menu, radio button, status bar, dock panel. If you want to help in writing them or have great ideas to improve project, write me email to elwood.su@gmail.com or connect via github.