Click here to Skip to main content
12,621,744 members (35,781 online)
Click here to Skip to main content
Add your own
alternative version

Stats

275.8K views
18.7K downloads
381 bookmarked
Posted

Embedding a Console in a C# Application

, 22 Mar 2015 MIT
Rate this:
Please Sign up or sign in to vote.
Embed a functional console window in a C# application.
 

Introduction

As developers, every so often we have to kick off some command line programs. Compiling and building tools, utilities,and so on. Recently I have been writing a small application that calls Oracle's 'impdp' and 'expdp' tools for you. While working on it I thought - it'd be nice to see the console program running, but inside my application. This article shows you how to create a Console Control that lets you do just that.

 

Tip: The ConsoleControl is available for both WPF and WinForms. 

You can follow the project on GitHub at https://github.com/dwmkerr/consolecontrol to track issues, suggest features and find documentation. 

 

Using the Control

 

Tip: Use Nuget! Install the package ConsoleControl for WinForms or ConsoleControl.WPF for WPF. 

 

If you just need the control, download the binary and sample application and add a reference to ConsoleControl.dll. You'll get the ConsoleControl in the toolbox, drop it into a Form, and you're done. The key function is:

StartProcess(string fileName, string arguments)

This function starts the process specified by fileName. It will use the arguments supplied. The process will run, but its output will be sent to the console control. The console control can also take input from the keyboard.

Some other useful functions are:

StopProcess

Kills the running process.

WriteOutput(string output, Color color)

Writes the string 'output' to the console window, using the specified color.

WriteInput(string input, bool echo, Color color)

Writes the string 'input' to the running process' input stream. If 'echo' is true, then the input will also be written to the Console Control (as if the user had keyed it in).

ShowDiagnostics

If this property is set to true, diagnostics will be shown. These include:

  • A message saying 'Running X' when you start a process.
  • A message saying 'X exited.' when a process ends.
  • Exceptions written in red if the process cannot be run.

IsInputEnabled

If this property is set to true, then the user can type input into the console window.

IsProcessRunning

This property evaluates to true if the the process is currently running.

CurrentProcess

The current running process (if any).

InternalRichTextBox

Returns the rich text box which displays the console output.

How Does It Work?

Essentially all we need to do to get the required functionality, is create a process and have direct access to its input and output streams. Here's how we can do that. Create a new User Control named 'ConsoleControl' and add a rich text box to the UserControl. Now we'll create the most key function - StartProcess:

/// <summary>
/// Runs a process.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <param name="arguments">The arguments.</param>
public void StartProcess(string fileName, string arguments)
{
    //  Create the process start info.
    var processStartInfo = new ProcessStartInfo(fileName, arguments);

    //  Set the options.
    processStartInfo.UseShellExecute = false;
    processStartInfo.ErrorDialog = false;
    processStartInfo.CreateNoWindow = true;

    //  Specify redirection.
    processStartInfo.RedirectStandardError = true;
    processStartInfo.RedirectStandardInput = true;
    processStartInfo.RedirectStandardOutput = true;

The first thing we do is create the process start info. To make sure that it doesn't show a window, we make sure CreateNoWindow is set to true. We will also specify that we're redirecting standard output, standard error, and standard input.

//  Create the process.
currentProcess = new Process();
currentProcess.EnableRaisingEvents = true;
currentProcess.StartInfo = processStartInfo;
currentProcess.Exited += new EventHandler(currentProcess_Exited);

//  Try to start the process.
try
{
    bool processStarted = currentProcess.Start();
}
catch (Exception e)
{
    //  We failed to start the process. Write the output (if we have diagnostics on).
    if(ShowDiagnostics)
        WriteOutput("Failed: " + e.ToString() + Environment.NewLine, Color.Red);
    return;
}

//  Store name and arguments.
currentProcessFileName = fileName;
currentProcessArguments = arguments;

Now that we have the process start info, we can actually start the process. We also store the file name and arguments for later. The class must also contain two background workers, to read the output stream and error stream. Now we start them - and enable editing if the IsInputEnabled flag is on. We also store the standard input, output and error streams.

    //  Create the readers and writers.
    inputWriter = currentProcess.StandardInput;
    outputReader = TextReader.Synchronized(currentProcess.StandardOutput);
    errorReader = TextReader.Synchronized(currentProcess.StandardError);

    //  Run the output and error workers.
    outputWorker.RunWorkerAsync();
    errorWorker.RunWorkerAsync();

    //  If we enable input, make the control not read only.
    if (IsInputEnabled)
        richTextBoxConsole.ReadOnly = false;
}

The background workers that read the standard input and error streams work in the same way, so we only need to see one of them:

/// <summary>
/// Handles the DoWork event of the outputWorker control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/>
///    instance containing the event data.</param>
void outputWorker_DoWork(object sender, DoWorkEventArgs e)
{
    //  Keep working until we're told to cancel.
    while (outputWorker.CancellationPending == false)
    {
        //  Just keep on reading the buffer.
        int count = 0;
        char[] buffer = new char[1024];
        do
        {
            //  Create a builder.
            StringBuilder builder = new StringBuilder();
            
            //  Read and append data.
            count = outputReader.Read(buffer, 0, 1024);
            builder.Append(buffer, 0, count);
            
            //  Report the progress.
            outputWorker.ReportProgress(
                  0, new OutputEvent() { Output = builder.ToString() });
        } while (count > 0);
    }
}

All we are doing here is reading from the specified buffer. We then use the 'ReportProgress' function of the worker to tell the control to update the screen. The Report Progress event handler works as below:

/// <summary>
/// Handles the ProgressChanged event of the outputWorker control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs"/>
///    instance containing the event data.</param>
void outputWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (e.UserState is OutputEvent)
    {
        //  Get the event data.
        var outputEvent = e.UserState as OutputEvent;

        //  Write the output.
        WriteOutput(outputEvent.Output, Color.White);
    }
}

/// <summary>
/// Writes the output to the console control.
/// </summary>
/// <param name="output">The output.</param>
/// <param name="color">The color.</param>
public void WriteOutput(string output, Color color)
{
    if (string.IsNullOrEmpty(lastInput) == false && output.Contains(lastInput))
        return;

    //  Invoke on the UI thread...
    Invoke((Action)(() =>
    {
        //  Write the output.
        richTextBoxConsole.SelectionColor = color;
        richTextBoxConsole.SelectedText += output;
        inputStart = richTextBoxConsole.SelectionStart;
    }));
}

The event handler is trivial - we just cast the data and write the output. Writing the output is just a case of adding the text to the rich text box. inputStart is also updated - this lets us keep track of where the user can type. This is used in the KeyDown event of the internal rich text box, as we see below.

/// <summary>
/// Handles the KeyDown event of the richTextBoxConsole control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
///    cref="System.Windows.Forms.KeyEventArgs"/>
///    instance containing the event data.</param>
void richTextBoxConsole_KeyDown(object sender, KeyEventArgs e)
{
    //  If we're at the input point and it's backspace, bail.
    if ((richTextBoxConsole.SelectionStart <= inputStart) && 
                e.KeyCode == Keys.Back) e.SuppressKeyPress = true;

    //  If we're before the input point and it's anything but arrows, bail.
    if ((richTextBoxConsole.SelectionStart < inputStart) &&
      !(e.KeyCode == Keys.Left || e.KeyCode == Keys.Right || 
                   e.KeyCode == Keys.Up || e.KeyCode == Keys.Down))
        e.SuppressKeyPress = true;

    if (e.KeyCode == Keys.Return)
    {
        //  Get the input.
        string input = richTextBoxConsole.Text.Substring(inputStart, 
               (richTextBoxConsole.SelectionStart) - inputStart);

        //  Write the input (without echoing).
        WriteInput(input, Color.White, false);

        //    We're done.
        e.SuppressKeyPress = true;
    }
}

If the selection is before the input start, we don't let the user enter text. If the return key is pressed, we write it to the output stream.

This is the bulk of the code - as you can see, it is not complex, but it does the job.

Final Thoughts

This control is basic - it may support what you need, if not do feel free to use it as a baseline for your own projects. Some things that the control doesn't do are listed below:

  1. Restrict output to 80 characters horizontally.
  2. Respect changes to the console foreground and background.
  3. Support programmatic clear-screen etc.. Remember - we're streaming output, if you need to run a program that manipulates the console, this is not the control for you!

Ctrl-C  

There has been quite some discussion in the messages about the complexity of sending a command like 'Control C' to a cmd process in the console window. Just so that others can find the solution until something formal is actually published, VisualG seems to have a good solution which you can find here: 

http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application?msg=4320232#xx4320232xx 

Scroll to Bottom 

Again, thanks to VisualG for the solution that let's you automatically scroll to the bottom - this'll come in a release soon! Until then, see the message below:

http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application?msg=4642047#xx4642047xx 

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Dave Kerr
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralMy vote of 5 Pin
JF201514-Aug-12 19:01
memberJF201514-Aug-12 19:01 
GeneralRe: My vote of 5 Pin
Dave Kerr14-Aug-12 23:09
mvpDave Kerr14-Aug-12 23:09 
GeneralMy vote of 5 Pin
Abdul Quader Mamun14-Aug-12 17:04
memberAbdul Quader Mamun14-Aug-12 17:04 
GeneralRe: My vote of 5 Pin
Abdul Quader Mamun14-Aug-12 17:05
memberAbdul Quader Mamun14-Aug-12 17:05 
GeneralRe: My vote of 5 Pin
Abdul Quader Mamun14-Aug-12 17:05
memberAbdul Quader Mamun14-Aug-12 17:05 
GeneralRe: My vote of 5 Pin
Dave Kerr14-Aug-12 23:08
mvpDave Kerr14-Aug-12 23:08 
GeneralMy vote of 5 Pin
Christian Amado14-Aug-12 10:50
memberChristian Amado14-Aug-12 10:50 
GeneralRe: My vote of 5 Pin
Dave Kerr14-Aug-12 23:07
mvpDave Kerr14-Aug-12 23:07 
QuestionWhat about powershell Pin
GSerjo29-Jul-12 10:12
memberGSerjo29-Jul-12 10:12 
AnswerRe: What about powershell Pin
Dave Kerr29-Jul-12 10:17
mvpDave Kerr29-Jul-12 10:17 
QuestionCtrl+C Pin
Gagan11187-May-12 9:17
groupGagan11187-May-12 9:17 
AnswerRe: Ctrl+C Pin
VizualG24-Jul-12 5:06
memberVizualG24-Jul-12 5:06 
GeneralRe: Ctrl+C Pin
Gagan111824-Jul-12 9:08
groupGagan111824-Jul-12 9:08 
GeneralRe: Ctrl+C Pin
VizualG25-Jul-12 6:16
memberVizualG25-Jul-12 6:16 
This is because u are getting the wrong handle.

What u need is the MainWindowHandle of the application the thread starts.
To get this try (assuming shellProcess is the actual Process object)

[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
 
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
 

private static void SendCtrlC(IntPtr hWnd)
{
    const uint keyeventfKeyup = 2;
    const byte vkControl = 0x11;
    //hWnd == handle to console window
    //set it to foreground or u can not send commands
    SetForegroundWindow(hWnd);
    //sending keyboard event Ctrl+C
    keybd_event(vkControl, 0, 0, 0);
    keybd_event(0x43, 0, 0, 0);
    keybd_event(0x43, 0, keyeventfKeyup, 0);
    keybd_event(vkControl, 0, keyeventfKeyup, 0);
}
 

 
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
 
private void ShowConsoleWindow()
{
    if (shellProcess.HasExited) return;
    while (shellProcess.MainWindowHandle == IntPtr.Zero)
{
//wait (do not thread.sleep here, it will auto release on !IntPtr.Zero(when it gets the handle))
    }
 
    shellProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
    ShowWindow(shellProcess.MainWindowHandle, 1);
}
 
private void SendShutdownToConsole()
{
    if (shellProcess.HasExited) return;
    while (shellProcess.MainWindowHandle == IntPtr.Zero)
{
//wait (do not thread.sleep here, it will auto release on !IntPtr.Zero(when it gets the handle))
}
 
    SendCtrlC(shellProcess.MainWindowHandle);
}
 
public void Terminate()
{
    //first get is back to foreground
    ShowConsoleWindow();
    //then send terminate command
    SendShutdownToConsole();
}

GeneralRe: Ctrl+C Pin
Gagan11181-Aug-12 8:39
groupGagan11181-Aug-12 8:39 
GeneralRe: Ctrl+C Pin
VizualG2-Aug-12 0:54
memberVizualG2-Aug-12 0:54 
GeneralMy vote of 5 Pin
Akram El Assas25-Apr-12 12:19
memberAkram El Assas25-Apr-12 12:19 
GeneralRe: My vote of 5 Pin
Dave Kerr16-May-12 4:09
mvpDave Kerr16-May-12 4:09 
GeneralMy vote of 5 Pin
guidy30-Mar-12 21:07
memberguidy30-Mar-12 21:07 
GeneralRe: My vote of 5 Pin
Dave Kerr19-Apr-12 2:47
mvpDave Kerr19-Apr-12 2:47 
GeneralMy vote of 5 Pin
Wooters30-Mar-12 15:14
memberWooters30-Mar-12 15:14 
GeneralRe: My vote of 5 Pin
Dave Kerr19-Apr-12 2:46
mvpDave Kerr19-Apr-12 2:46 
GeneralMy vote of 5 Pin
Mihai MOGA11-Mar-12 16:44
memberMihai MOGA11-Mar-12 16:44 
GeneralRe: My vote of 5 Pin
Dave Kerr19-Apr-12 2:46
mvpDave Kerr19-Apr-12 2:46 
GeneralMy vote of 5 Pin
C_Johnson8-Mar-12 23:20
memberC_Johnson8-Mar-12 23:20 
GeneralRe: My vote of 5 Pin
Dave Kerr19-Apr-12 2:46
mvpDave Kerr19-Apr-12 2:46 
GeneralMy vote of 5 Pin
seer_tenedos27-Mar-12 11:01
memberseer_tenedos27-Mar-12 11:01 
GeneralRe: My vote of 5 Pin
Dave Kerr19-Apr-12 2:46
mvpDave Kerr19-Apr-12 2:46 
QuestionGreat article! Pin
Aamer Alduais7-Mar-12 8:23
memberAamer Alduais7-Mar-12 8:23 
AnswerRe: Great article! Pin
Dave Kerr19-Apr-12 2:46
mvpDave Kerr19-Apr-12 2:46 
GeneralMy vote of 5 Pin
Rak_i6-Mar-12 10:26
memberRak_i6-Mar-12 10:26 
GeneralRe: My vote of 5 Pin
Dave Kerr6-Mar-12 22:15
mvpDave Kerr6-Mar-12 22:15 
Questionvery nice Pin
CIDev6-Mar-12 4:08
memberCIDev6-Mar-12 4:08 
AnswerRe: very nice Pin
Dave Kerr6-Mar-12 4:36
mvpDave Kerr6-Mar-12 4:36 
GeneralMy vote of 5 Pin
riyadoll5-Mar-12 19:32
memberriyadoll5-Mar-12 19:32 
GeneralRe: My vote of 5 Pin
Dave Kerr6-Mar-12 2:47
mvpDave Kerr6-Mar-12 2:47 
GeneralMy vote of 4 Pin
Cristian Amarie3-Mar-12 20:12
memberCristian Amarie3-Mar-12 20:12 
GeneralRe: My vote of 4 Pin
Dave Kerr5-Mar-12 2:32
mvpDave Kerr5-Mar-12 2:32 
GeneralRe: My vote of 4 Pin
Cristian Amarie5-Mar-12 8:53
memberCristian Amarie5-Mar-12 8:53 
GeneralMy vote of 5 Pin
Shameel1-Mar-12 18:28
memberShameel1-Mar-12 18:28 
GeneralRe: My vote of 5 Pin
Dave Kerr2-Mar-12 0:56
mvpDave Kerr2-Mar-12 0:56 
GeneralMy vote of 5 Pin
Jason Down1-Mar-12 3:48
memberJason Down1-Mar-12 3:48 
GeneralRe: My vote of 5 Pin
Dave Kerr1-Mar-12 4:43
mvpDave Kerr1-Mar-12 4:43 
GeneralMy vote of 5 Pin
ProEnggSoft28-Feb-12 23:34
memberProEnggSoft28-Feb-12 23:34 
GeneralRe: My vote of 5 Pin
Dave Kerr29-Feb-12 1:52
mvpDave Kerr29-Feb-12 1:52 
GeneralMy vote of 5 Pin
johannesnestler27-Feb-12 9:28
memberjohannesnestler27-Feb-12 9:28 
GeneralRe: My vote of 5 Pin
Dave Kerr28-Feb-12 1:13
mvpDave Kerr28-Feb-12 1:13 
QuestionLove it, but tab completion doesn't seem to work Pin
thebeekeeper27-Feb-12 8:10
memberthebeekeeper27-Feb-12 8:10 
AnswerRe: Love it, but tab completion doesn't seem to work Pin
Dave Kerr27-Feb-12 23:07
mvpDave Kerr27-Feb-12 23:07 
GeneralRe: Love it, but tab completion doesn't seem to work Pin
Member 81488521-Mar-12 12:35
memberMember 81488521-Mar-12 12:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.161128.1 | Last Updated 22 Mar 2015
Article Copyright 2012 by Dave Kerr
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid