ShellControl - A console emulation control






4.61/5 (43 votes)
A .NET Control that emulates a command line UI
Introduction
Here is a good question for the CodeProject poll. How many of us have wanted to write an interpreter for a language of our own? I'm sure that would include most of us. All of us who actually tried to write one would have written it as a command line application. What if I wanted to embed it in a Windows application? Out of that question was born this control, which I call the ShellControl
. It has nothing to do with the OS shell, although it can be used as a UI for one. In fact, the sample application does just that, it gets commands from the user, uses cmd.exe to run it, and displays the results in the UI. Think of it as the command tool window in Visual Studio.
What it is?
The ShellControl
is a .NET User Control that you can embed directly in your Windows Forms applications. It emulates a command line UI, like the DOS shell. It is pretty basic in functionality. It provides the following features:
- Prompt - It provides a prompt, which can be changed anytime. It works like the prompt we are used to see in the DOS shell, i.e., it can't be erased out.
- Command History - It maintains a history of the commands executed so far. The up/down arrow keys can be used to move up/down the history just like the DOS shell.
- Autocompletion - It provides auto completion for the last command. The right arrow key can be used for this purpose.
- All other features of a standard
TextBox
, because theShellControl
in reality is just a modifiedTextBox
, it provides all the normal functionality of the standard WindowsTextBox
. This includes using the mouse to select text, copy/paste, undo/redo etc.
How to use it?
Pretty simple. Add the control like any normal User Control (Right click on the ToolBox, click Add/Remove Items and Browse to ShellControl.dll). Subscribe to the CommandEnteredEvent
. Do whatever you want with the command text passed in as part of the CommandEnteredEventArgs
object. Here is some sample code:
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
shellControl1.CommandEntered += new
UILibrary.EventCommandEntered(shellControl1_CommandEntered);
}
void shellControl1_CommandEntered(object sender,
UILibrary.CommandEnteredEventArgs e)
{
string command = e.Command;
if (!ProcessInternalCommand(command))
{
ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe");
startInfo.Arguments = "/C " + e.Command;
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
Process p = Process.Start(startInfo);
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
if (output.Length != 0)
shellControl1.WriteText(output);
else if (error.Length != 0)
shellControl1.WriteText(error);
}
}
The code above gets the command text and executes it by starting a new cmd.exe process. It then writes back the result to the control. It can't get any simpler, can it?
Other Properties And Methods
Besides the event, the control exposes the following properties and methods:
Properties
Prompt
- Use it to get/set the prompt displayed in the control.ShellTextForeColor
- Set/Get the color of the foreground text.ShellTextBackColor
- Set/Get the background color.ShellTextFont
- Set/Get the font of the console text.
Methods
WriteText(string text)
- Use it to write text to theShellControl
. You need to remember that the control writes the prompt on the display after every call toWriteText
, so if you are writing multiple lines, be sure to append them to a single string before callingWriteText
.Clear
- Clears the console, leaving just the prompt.GetCommandHistory
- Returns a string array of the commands entered so far.
How it works?
The ShellControl
is a class derived from UserControl
. All it does is wrap ShellTextBox
, which derives from TextBox
. Two questions here.
- Why not directly expose
ShellTextBox
?Because that would expose all properties of the
TextBox
, includingMultiLine
,Text
,MaxLength
etc. And that would be disastrous because theShellControl
needs to tightly control the addition of text to make sure that nothing funny happens. For e.g., theShellControl
needs to make sure that you can't modify text that's above the current line, it needs to ensure that you can't erase the prompt and so on.. - Why derive from
TextBox
, why not aggregate it?Because
ShellTextBox
needs to overrideWndProc
. And that's necessary to make sure that Cut/Paste/Clear from the Context menu don't do anything silly.
The overridden WndProc
looks like this:
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case 0x0302: //WM_PASTE
case 0x0300: //WM_CUT
case 0x000C: //WM_SETTEXT
if (!IsCaretAtWritablePosition())
MoveCaretToEndOfText();
break;
case 0x0303: //WM_CLEAR
return;
}
base.WndProc(ref m);
}
As you can see, WM_CLEAR
has no effect, while Cut/Paste are made to operate on the text currently at the prompt.
For controlling key presses, the ShellTextBox
subscribes to the KeyPress
and KeyDown
events of the TextBox
. The KeyPress
event handler handles backspace and Enter keys while other keys are handled by the KeyDown
event handler. A utility class, CommandHistory
, maintains the list of commands entered as well as the current position in that list. The up/down arrow keys use information in that list to display the command history. That section looks like this:
private void ShellControl_KeyDown(object sender, KeyEventArgs e)
{
// If the caret is anywhere else, set it back
// when a key is pressed.
if (!IsCaretAtWritablePosition() &&
!(e.Control || IsTerminatorKey(e.KeyCode)))
{
MoveCaretToEndOfText();
}
// Prevent caret from moving before the prompt
if (e.KeyCode == Keys.Left && IsCaretJustBeforePrompt())
{
e.Handled = true;
}
else if (e.KeyCode == Keys.Down)
{
if (commandHistory.DoesNextCommandExist())
{
ReplaceTextAtPrompt(commandHistory.GetNextCommand());
}
e.Handled = true;
}
else if (e.KeyCode == Keys.Up )
{
if (commandHistory.DoesPreviousCommandExist())
{
ReplaceTextAtPrompt(commandHistory.GetPreviousCommand());
}
e.Handled = true;
}
else
{
// Some more code here..
}
}
When the enter key is pressed, the ShellTextBox
gets the text currently at the prompt and calls FireCommandEntered
on ShellTextControl
, which in turn fires the CommandEntered
event. That's all there is to it.
Conclusion
Although I'm fairly comfortable in C#, this is my first attempt at creating UserControls. I know this is not going to be the most downloaded control in CodeProject, but I feel it's still useful for certain situations where you want to emulate the command line.
Feedback, comments are welcome.
History
Feb 17 2005 - Initial submission.