So you want to make a Command Line Interface

Diablo

New member
command.png


[Keba] not only asked Answeres.HackaDay.com, but also sent us an email as follows.

“Can you make a basic guide to designing a good Command Line User Interface?”

Wouldn’t you know the luck, I’m currently working on a Command Line type interface for a project of mine. While after the jump I’ll be walking through my explanation, it should be noted that the other replies to Answers.HackaDay.com are also great suggestions.



We have no real idea how [Keba] intends to implement a system for the ATmega16 (Serial display? Output to an LCD? etc?), but for my project it is as follows. Using C# along with DirectX (can you tell I’m making a game with a developer console?) I’ll display an input line, suggestions for inputs (intellisense), and outputs based only when a correct input is given.

To begin, and to stay focused on only the CLI, I’ll assume your project has all the necessary startup and load functions. In my case, loading of a DX device, and input handling. Also, we assume you know how to program in your respective language.

I’ll be using a pretty advanced technique (StringBuilder) for string handling, because traditional string + string concatenation is terrible on memory (and games need as much as they can get). If you don’t care for memory, you can simply use regular strings.

To start off we’ll need some global variables,

public bool bool_isConsoleOpen = false; //console, also known as CLI public StringBuilder StringBuilder_Console = new StringBuilder(); //could be replaced with string public InputDevice ID = new InputDevice(); Within the main function loop, make a call to a method named UpdateConsole();

Now, in my setup to prevent unwanted user input there is a small check to see if the console is ‘open’ or ‘closed’.

public void UpdateConsole() { //opening console if (ID.isKeyDown(Keys.Oemtilde) && ID.isOldKeyUp(Keys.Oemtilde)) if (bool_isConsoleOpen == false) { bool_isConsoleOpen = true; //user pressed magic key, open console StringBuilder_Console = new StringBuilder(); //clear string } else bool_isConsoleOpen = false; //user pressed magic key, close console } The next section of code handles all the inputs (keyboard presses) and builds our string that is about to be entered. It includes support for shift capitals, pasting from the clipboard, and also checks to make sure each key entered is allowed. Simply add this portion immediately after bool_isConsoleOpen = false;.

//appending console if its open. if (bool_isConsoleOpen == true) { bool caps = false; //variable that helps determine if shift is pressed if (ID.isKeyDown(Keys.ShiftKey)) caps = true; List pressedkeystemp = ID.PressedKeys; //I had to modify my ID a bit to make it get a list/array of the keys pressed. //go through each new key in list foreach (Keys currentkey in pressedkeystemp) { //make a string, this is for numbers string key; //if the key SPACE is pressed, make a space if (currentkey == Keys.Space) { StringBuilder_Console.Append(" "); } //if the key BACK is pressed, backspace else if (currentkey == Keys.Back) { if (StringBuilder_Console.Length > 0) StringBuilder_Console.Remove(StringBuilder_Console.Length - 1, 1); } //if enter is pressed else if (currentkey == Keys.Enter) { //send it off to apply our data ApplicationSettings(StringBuilder_Console.ToString()); //clear our string StringBuilder_Console = new StringBuilder(); } //if a number is pressed, make it show up else if (StringKeyINTCheck(currentkey, out key)) { StringBuilder_Console.Append(key); } //if a-z is pressed, make it show up else if (StringKeyCheck(currentkey)) { // if V was just pressed and either control key is down if (currentkey == Keys.V && (ID.isKeyDown(Keys.ControlKey))) { // paste time! string pastevalue = ""; pastevalue = System.Windows.Forms.Clipboard.GetText(System.Windows.Forms.TextDataFormat.Text); StringBuilder_Console.Append(pastevalue); } // if not pasting, do a regular key else if (!caps) StringBuilder_Console.Append(currentkey.ToString().ToLower()); else if (caps) StringBuilder_Console.Append(currentkey.ToString()); } } In order to prevent some characters from being printed, such as alt characters, and to make sure the input key can actually be displayed (otherwise you could crash with error) I implement a few checks. You’ll notice I have two different types, Check(input, output) and Check(input). The former is necessary because often the input is the ASCII value, and needs to be converted to a char or string before being added to the builder. The latter simply returns true or false if the key is valid.

Example of the first, numerals

//numerals private bool StringKeyINTCheck(Keys key, out string i) { if (key == Keys.D1 || key == Keys.NumPad1) { i = "1"; return true; } else if (key == Keys.D2 || key == Keys.NumPad2) { i = "2"; return true; } etc... } And the latter, a-z

private bool StringKeyCheck(Keys key) { if (key == Keys.A || key == Keys.B || key == Keys.C || etc... key == Keys.X || key == Keys.Y || key == Keys.Z) return true; else return false; } So now we have our string built, you’ll notice the new method ApplicationSettings(string) is called whenever enter is pressed. This is the sending off of the string the user just typed in/that we built, we must now break that string down and determine what the user typed, and what should happen.

Once again, I start off with a few checks, just to prevent crashes.

private void ApplicationSettings(string temp) { if (temp != null) //make sure the user didn't type in "". { //make it all lower case temp = temp.ToLower(); //split by spaces string[] words = temp.Split(' '); } } Now comes the fun part, We’ve assumed the user has entered things such as “quit” “fullscreen 1″ and “pos 100x100x100″. The first will quit the application, the second will determine if the application should be fullscreen or not. And the final sets the users XYZ position in space. These three are simply examples of multiple variable entry, and you could of course program whatever you need.

Immediately after string[] words = temp.Split(‘ ‘); add the following,

try { //quit exit if (words[0] == "quit" || words[0] == "exit") this.Close(); //check for users fullscreen preference else if (words[0] == "fullscreen") { if (words[1] == "0") WindowedMode = true; //arbitrary global named windowedMode else if (words[1] == "1") WindowedMode = false; } //set the camera position else if (words[0] == "pos") { if (words[1].Contains("x")) { string[] res = words[1].Split('x'); int int_x = Convert.ToInt32(res[0]); int int_y = Convert.ToInt32(res[1]); int int_z = Convert.ToInt32(res[2]); Cam.Position = new Vector3(int_x, int_y, int_z);//arbitrary class camera Cam } } } catch (IndexOutOfRangeException e) { //this occurs when the user types "fullscreen $". Where $ is a variable, and the user typed nothing. //do nothing we should tell the user this with an error message. } catch (FormatException e) { //this occurs when the user types "resolution $x$", where $ is an int variable, and the user typed alpha. //do nothing we should tell the user this with an error message. } You probably could stop here if needed, you have input and output. However, I have something like 40 different commands in the current revision of my console, I couldn’t remember them all. So I made my own nifty intellisense.

This is going to require setting up another global–string list, filling it with commands, and then alphabetizing it.

List ListString_Console = new List(); private void LoadConsoleWordList() { ListString_Console.Clear(); //load in our console! ListString_Console.Add("fullscreen"); ListString_Console.Add("resolution"); ListString_Console.Add("showfps"); //ListString_Console.Add("vertsync"); ListString_Console.Add("maxfps"); ListString_Console.Add("quit"); ListString_Console.Add("exit"); ListString_Console.Add("saveconsole"); //ListString_Console.Add("bind"); etc... //sort our list ListString_Console.Sort(); } Now at the bottom of our UpdateConsole().

if (bool_isConsoleOpen == true) { BMF_Arial.AddString(StringBuilder_Console.ToString() + "_", "console", new System.Drawing.RectangleF(5, 18, Resolution.Width, 20)); //how I draw things to the screen in DX. StringBuilder_Console is the string we built earlier, so the user can see what he is typing. //help our user search. int q = 35; //check every single string we know against what the user is typing in foreach (string stringy in ListString_Console) { //so long as the length is right, we continue if (stringy.Length >= StringBuilder_Console.Length) //this part could be eliminated, and we could simply go through every letter. But this speeds up operations a smidge. { //temporary bool bool hodling = false; //go through every letter for (int i = 0; i < StringBuilder_Console.Length; i++) if (stringy == StringBuilder_Console) hodling = true; else { hodling = false; break; } //if it's a 100% match if (hodling) { //draw it, and update q relative. BMF_Arial.AddString(stringy, "console", new RectangleF(5, 2 + q, Resolution.Width, 20)); //these are all the matches to the currently types string. q += 18; } } } } So how does it finally look?

No console open,



Hitting the magical key opens up console, begin typing, see intellisense,



Continue typing, other words that don’t match get taken off display,



and hitting enter executes the command,




b.gif
 
Back
Top