Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ShellControl - A console emulation control

0.00/5 (No votes)
26 Feb 2005 1  
A .NET Control that emulates a command line UI

Screenshot

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 the ShellControl in reality is just a modified TextBox, it provides all the normal functionality of the standard Windows TextBox. 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 the ShellControl. You need to remember that the control writes the prompt on the display after every call to WriteText, so if you are writing multiple lines, be sure to append them to a single string before calling WriteText.
  • 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.

  1. Why not directly expose ShellTextBox?

    Because that would expose all properties of the TextBox, including MultiLine, Text, MaxLength etc. And that would be disastrous because the ShellControl needs to tightly control the addition of text to make sure that nothing funny happens. For e.g., the ShellControl 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..

  2. Why derive from TextBox, why not aggregate it?

    Because ShellTextBox needs to override WndProc. 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here