38

I'm building this application in Visual Studio 2010 using C#.

Basically there are 2 files, form1.cs (which is the windows form) and program.cs (where all the logic lies).

//form1.cs
public partial class Form1 : Form
{
    //runButton_click function
}

//program.cs
class Program
{
    static void Main()
    {
        while(blah-condition)
        {
            //some calculation
            Console.WriteLine("Progress " + percent + "% completed.");
        }
    }
}

There is a Run button and a blank textbox.

When the user hits the Run button, program.cs will perform some task and constantly printing out the progress using Console.WriteLine() onto the console (command prompt).

Question: How can I print to the textbox on form1 instead of printing into command prompt? I will need to print the progress constantly without any user action.

Thanks in advance!

By the way, it doesn't have to be a textbox, it can be a label or something else that can take text. I chose textbox because it makes more sense to me.

sora0419
  • 2,308
  • 9
  • 39
  • 58
  • 1
    While your original question seems to have been answered, beware if you are sending TONS of updates to the textbox for you may flood your message queue causing the form to become quite unresponsive. Form responsiveness also depends on how intense "some calculation" is. You may want to consider a backgroundworker thread that reports progress back to the form. – Rick Davin Sep 10 '13 at 19:28
  • 1
    @RickDavin Note that such an alternative would require have the abilitly to change all of the console writes to something else. If he doesn't have control over that code (i.e. it's library code) that may not be an option, or it may just not be practical. – Servy Sep 10 '13 at 19:30

4 Answers4

76

Start by creating a new TextWriter that is capable of writing to a textbox. It only needs to override the Write method that accepts a char, but that would be ungodly inefficient, so it's better to overwrite at least the method with a string.

public class ControlWriter : TextWriter
{
    private Control textbox;
    public ControlWriter(Control textbox)
    {
        this.textbox = textbox;
    }

    public override void Write(char value)
    {
        textbox.Text += value;
    }

    public override void Write(string value)
    {
        textbox.Text += value;
    }

    public override Encoding Encoding
    {
        get { return Encoding.ASCII; }
    }
}

In this case I've had it just accept a Control, which could be a Textbox, a Label, or whatever. If you want to change it to just a Label that would be fine.

Then just set the console output to a new instance of this writer, pointing to some textbox or label:

Console.SetOut(new ControlWriter(textbox1));

If you want the output to be written to the console as well as to the textbox we can use this class to create a writer that will write to several writers:

public class MultiTextWriter : TextWriter
{
    private IEnumerable<TextWriter> writers;
    public MultiTextWriter(IEnumerable<TextWriter> writers)
    {
        this.writers = writers.ToList();
    }
    public MultiTextWriter(params TextWriter[] writers)
    {
        this.writers = writers;
    }

    public override void Write(char value)
    {
        foreach (var writer in writers)
            writer.Write(value);
    }

    public override void Write(string value)
    {
        foreach (var writer in writers)
            writer.Write(value);
    }

    public override void Flush()
    {
        foreach (var writer in writers)
            writer.Flush();
    }

    public override void Close()
    {
        foreach (var writer in writers)
            writer.Close();
    }

    public override Encoding Encoding
    {
        get { return Encoding.ASCII; }
    }
}

Then using this we can do:

Console.SetOut(new MultiTextWriter(new ControlWriter(textbox1), Console.Out));
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Do I put "Console.SetOut(new TextboxWriter(textbox1));" inside the the Main() withing program.cs? program.cs cannot recognize textbox1 which is in form1.cs and does not exist in program.cs – sora0419 Sep 10 '13 at 19:20
  • @sora0419 No, you'd put that within the form's definition. – Servy Sep 10 '13 at 19:20
  • when I create ControlWriter, i got an error saying "'ControlWriter' does not implement inherited abstract member 'System.IO.TextWriter.Encoding.get'" what am I missing? – sora0419 Sep 10 '13 at 19:27
  • @sora0419 Just add an implementation for the encoding. – Servy Sep 10 '13 at 19:29
  • I tried the first method. The program stopped printing to the console, however, nothing is printed in the textbox either. I put the ControlWriter class inside namespace Form1 but outside public partial class Form1 : Form{}. I put "Console.SetOut(new ControlWriter(textbox1));" inside Form1 class right after InitializeComponent(); Am I misunderstanding something and putting function in the wrong spot? – sora0419 Sep 10 '13 at 19:39
  • @sora0419 If the code is called from a non-UI thread it could be generating errors as a result of trying to access the form from a non-UI thread. If that's the case, you may need to invoke to the UI thread, and that would have significant performance consequences if you need to do that. – Servy Sep 10 '13 at 19:46
  • okay, I think that's the reason then. The application was built as a console app first then created a GUI for it. So it's using Application.Run(Form1); How would I go about invoking? Will I have to rewrite a lot of the code? I didn't build the application, I was ask to build a GUI for the existing app to improve UX so I'm afraid that if I have to rewrite a lot I might break something. Thank you so much for helping. – sora0419 Sep 10 '13 at 19:56
  • @sora0419 You can start by using `textbox.Invoke` within `Write` of the `ControlWriter`, and only manipulating the control within that. You'd need to do that for both methods. It's pretty likely to really kill performance though, if there is a lot of writes to standard output. If there isn't a lot of writing then it may not be so bad. – Servy Sep 10 '13 at 19:58
  • is it supposed to work with multithreaded applications? Cause I got a `System.InvalidOperationException: 'Cross-thread operation not valid: Control 'outputTextBox' accessed from a thread other than the thread it was created on.'` when, well, some other thread tried to write to console – elena Apr 23 '20 at 11:46
  • @elena You'd need to capture the currently synchronization context in the constructor and post to when interacting with the control if that's something you need to support. – Servy Apr 23 '20 at 19:20
2

I use sth like this for a listbox:

    public class ListBoxWriter : TextWriter //this class redirects console.writeline to debug listbox
{
    private readonly ListBox _list;
    private StringBuilder _content = new StringBuilder();

    public ListBoxWriter(ListBox list)
    {
        _list = list;
    }

    public override Encoding Encoding
    {
        get { return Encoding.UTF8; }
    }
    public override void Write(char value)
    {
        base.Write(value);
        _content.Append(value);

        if (value != '\n') return;
        if (_list.InvokeRequired)
        {
            try
            {
                _list.Invoke(new MethodInvoker(() => _list.Items.Add(_content.ToString())));
                _list.Invoke(new MethodInvoker(() => _list.SelectedIndex = _list.Items.Count - 1));
                _list.Invoke(new MethodInvoker(() => _list.SelectedIndex = -1));
            }
            catch (ObjectDisposedException ex)
            {
                Console.WriteLine(Resources.Exception_raised + " (" + ex.Message + "): " + ex);
            }
        }
        else
        {
            _list.Items.Add(_content.ToString());
            _list.SelectedIndex = _list.Items.Count - 1;
            _list.SelectedIndex = -1;
        }
        _content = new StringBuilder();
    }
}

and in my main application:

_writer = new ListBoxWriter(DebugWin); // DebugWin is the name og my listbox

Console.SetOut(_writer);

user38723
  • 39
  • 2
1

Don't know if it will work, but you could try to redirect console output.

Use Console.SetOut() and create derivative of TextWriter which overrides WriteLine() method and simply assign method parameter to your TextBox.Text Should work.

Raz Luvaton
  • 3,166
  • 4
  • 21
  • 36
quirell
  • 245
  • 1
  • 18
-3

put textbox on the form ( multiple lines enabled) or text area then you can do in your loop

txtOutput.Text += "Progress " + percent + "% completed." + Environment.NewLine();
Raz Luvaton
  • 3,166
  • 4
  • 21
  • 36
Dan Hunex
  • 5,172
  • 2
  • 27
  • 38