Redirecting Console.WriteLine() to Textbox
Asked Answered
M

4

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.

Metzger answered 10/9, 2013 at 19:1 Comment(2)
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.Laural
@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.Heptad
H
78

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));
Heptad answered 10/9, 2013 at 19:16 Comment(10)
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.csMetzger
@Metzger No, you'd put that within the form's definition.Heptad
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?Metzger
@Metzger Just add an implementation for the encoding.Heptad
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?Metzger
@Metzger 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.Heptad
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.Metzger
@Metzger 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.Heptad
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 consoleVachil
@Vachil 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.Heptad
S
2

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.

Spiracle answered 10/9, 2013 at 19:9 Comment(0)
A
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);

Alsoran answered 20/6, 2014 at 12:22 Comment(0)
B
-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();
Berry answered 10/9, 2013 at 19:9 Comment(2)
The problem is, the loop is in program.cs, and it cannot recognize the textbox, the textbox only exist in form1.csMetzger
Dont use console application then...just use a simple GUI app to do thatBerry

© 2022 - 2024 — McMap. All rights reserved.