Introducing Silverlight Console

Posted by Rishi on 29-Dec-09 6:15 AM - Comments (2)

Consoles are a unique animal in this day and age of multi-touch user interfaces - they stand productively tall despite all the enhancements in user-interaction over the years. And as Silverlight grows into a more rounded and generalized platform, there is a case for Command Line Interface tooling - so here is an attempt at a relatively simple implementation of a Silverlight based Console that includes a Console Window Control and a matching set of extensible Commands Framework that allows for executable commands (called script-actions) vocabulary.

SilverlightConsole3

View the demo application here.
Note, the demo application only supports a limited set of commands, type "help" for listing of available commands, for some examples try "setwallpaper -url:http://adsoftheworld.net/download/windows7/winwall7057_17large.jpg"  or "countdown 00:00:10" or "beep" to make a beeping sound.

Console Window Control (Orktane.Console.dll)

In terms of user-interaction the console is a bit of peculiar control, it isn't exposed as a directly usable control but rather is accessible only through a singular static Console class. For the purposes of the Silverlight Console, I've encapsulated the Console class functionality into an IConsole interface, which in turn is visualized and exposed through a Console Window user-control. The idea of an IConsole interface, just like in Windows, is to enable visually-independent consumption of the console functionality, and at the same time also allow having multiple instances of consoles within a single Silverlight application. Now, Silverlight does have a System.Console class but it is cocooned in the SecurityCritical attribute making it useless outside its internal uses.

ConsoleModel

The IConsole functionality above is much simplified compared to the Windows counterpart, we don't deal with screen-buffers, cursors, or position related settings. Also, though internally we do have In and Out streams they are not exposed, and rather than width-by-height buffer size we deal with a string length output buffer. The simplification is in part because the text-rendering functionality in Silverlight is basically all internal, so we are limited to Textbox or Textblock controls for text-based interaction. However with the RichTextArea control in Silverlight 4, it should enable a much richer experience (see Pose Console in WPF). Further, a solution based on column-by-row screen buffer would have been very resource-heavy for Silverlight, so instead we've used a string-based buffer - which you can set by using the SetOutBuffer method.

One other critical difference between the Windows counterpart is that Silverlight doesn't allow blocking calls, so returning values through ReadLine and ReadKey functionality isn't possible, hence ReadKey and ReadLine calls above return void. In place of returning values, both the ReadKey and ReadLine functionality take in an Action delegate which is used to yield the user input - the ReadKey takes in an Action of ConsoleKeyInfo (shown above) delegate and ReadLine takes an Action of String delegate. Now, whenever you set the ReadKey or the ReadLine delegate it switches the input mode between the two, and whenever you set the delegate it would keep sending the input to the same delegate for each input until changed.

Another issue with using the text-controls in Silverlight is that we have to manually disable input, for which you have to use the DisableInput property on the IConsole interface. This takes away the input Textbox, and if you choose you can display alternate text using the DisableInputText property - so for example below we have disabled the input whilst we are downloading in the background and erstwhile we are showing the progress info using the DisableInputText. Note the red indicator on the left of the text, which visually is a separate area (Textbox actually) from the console output area (also a disabled Textbox).

DisabledInputText 

Now to visually realize the IConsole we have the ConsoleWindow user-control that you can directly use, it has a Console property of type IConsole which allows for procedural use of the console. Internally, we have the console functionality separated from the visuals, a View-ViewModel kind of separation as shown below. You can potentially change the visuals or logic by changing either, or you can create your console-visuals using the ConsoleBase as a starting point.

ConsoleWindowModel

Note, since we are not using a cell based display, in the Console Window we use a fixed-width font, with the width pegged for 80 columns. This aspect is hard-coded, but it is consistent to the default Windows console.

Script-Actions Framework (Orktane.ScriptActions.dll)

By itself the ConsoleWindow implementation is dumb as a stick, it has no concept of executable commands, it just handles the input and output business. And so we have this Script Actions framework (or Commands framework), which basically extends the IConsole to allows execution of custom script-actions, where script-actions are executable commands (note, we don't use the word command because of the ICommand-related conation). In simpler terms you can think of script-actions as extensible vocabulary which is executable in a console. A

ScriptActionsModel

As seen above, we have two types of script-actions, one which can execute by itself (IScriptAction) and the other derivative (IConsoleScriptAction) that requires a console instance to execute. The Execute method takes in an Arguments-type parameter, which is a dictionary of parsed switches and command-line arguments. The command line parser is by Richard Lopes, with some additional word done by Jake Ginnivan. However, for a future release I am looking to port the Command Line Parser Library (by Jakub Malý), which allows for strongly-typed arguments and better structuring of script-actions.

Now, if your ScriptAction class implements the IConsoleScriptAction then the Console instance is passed in and the Initialize method is called prior to execution. And again because we don't have blocking calls in Silverlight you have to manually indicate as to when the script-action has finished its execution, for that you use the ExecutionCompleted event to flag the completion. The close method is called when you've finished, or earlier if the executing environment is itself closing. Further, you can also handle a cancel request (called using the Ctrl+C pairing) by listening to the IConsole's CancelKeyPress event. On the other hand, the IScriptAction based script-action are executed without any indication in the Console (note that is different from when a request command is not found), and once the execution has been called the console returns to an empty console prompt.

On of the features in nRoute, is a MEF like composition component called the Resource Locator - it basically allows you to earmark specific resources and have them cataloged. So based on that, we have a MapScriptAction attribute that flags an implementation of IScriptAction for use in a console; as an example consider:

   1: [MapScriptAction("GC", "GC Memory", 
   2:     ShortDescription = "Forces an immediate garbage collection.",
   3:     Lifetime = InstanceLifetime.PerInstance, UnListed = true)]
   4: public class GCScriptAction : IScriptAction
   5: {
   6:     public void Execute(Arguments args) { 
   7:         GC.Collect();
   8:     }
   9: }

Above, we an IScriptAction implementation that forces an immediate garbage collection on execution, and we have earmarked it with a MapScriptAction attribute. The attribute takes in couple of settings like a unique and callable command-name, a title for the script-action and other options like short-description, a lifetime setting indicating how to handle instantiation, and lastly an option as to if the command is not listed in the help listings. Using the attributes based composition of script-actions makes it painless to have commands availed at runtime or dynamically resourced, for more information please see Introduction to nRoute.Toolkit posts.

Lastly, once we have the script-actions we need to enable their use from within an IConsole instance, for that we have a behavior called ScriptActionConsoleBehavior that attaches to any IConsoleHost implementing (also must be a FrameworkElement) instance. The xaml for that looks like:

   1: <console:ConsoleWindow x:Name="consoleWindow">
   2:     <i:Interaction.Behaviors>
   3:         <scriptActions:ScriptActionConsoleBehavior/>
   4:     </i:Interaction.Behaviors>
   5: </console:ConsoleWindow>

As seen above, the separation of concerns in terms of the console's working-logic and the visuals allows you to plug-in your own custom logic or visual implementation. So for example you could have MEF composed script-actions, or bring Powershell scripts into Silverlight and hook them into a ConsoleWindow. Remember the pairing is necessary, as by itself the Console Window control is dumb-as-a-stick.

Summary

So what we have here is a two-piece solution, one an IConsole based visual control, and two a complimentary console-executable vocabulary of script-actions. And though the solution is not true to the Windows version, it within the limitations of Silverlight a very usable tool. Finally, if you have suggestions or ideas to enhance what's here please do let me know.

Source-code and binaries are available on Codeplex
and you can view the Console Demo Application here.