3

I have got an interesting dilemma where my application can run as a Console App or a Windows Forms App.

Since I do not want to write a shed load of code like this all over my application:

If ( IsConsoleApp() )
{
    // process Console input and output
}
else
{
    // process Windows input and output
}

To prevent this, I have decided to create two methods where I can pass in a TextReader and TextWriter instance and subsequently use these to process input and output, e.g.

public void SetOutputStream( TextWriter outputStream )
{
    _outputStream = outputStream;
}

public void SetInputStream( TextReader inputStream )
{
    _inputStream = inputStream;
}

// To use in a Console App:
SetOutputStream( Console.Out );
SetInputStream( Console.In );

To display some text in the Console window I just need to do something like this:

_outputStream.WriteLine( "Hello, World!");

And the text is magically redirected to the Console.

Now, my issue is how do I do something similar for a Windows application? I have created a form with a read-only Text Box control on it and I want the contents of the _outputStream to be redirected to this text box in real-time.

Also, I want the _inputStream to contain the contents of another Text Box control so that my App can read from this stream instead of the Text Box directly.

Thanks in advance.

Intrepid
  • 2,781
  • 2
  • 29
  • 54
  • That's a reasonable design. There aren't any streams built into the framework that connect directly to textboxes, but you can implement your own subclass pretty easily. And there probably are some third-party solutions with lots of features. – Ben Voigt Sep 12 '13 at 13:29
  • possible duplicate of [Bind Console Output to RichEdit](http://stackoverflow.com/questions/3744668/bind-console-output-to-richedit) – Hans Passant Sep 12 '13 at 13:31
  • @HansPassant I've taken a look at the link you provided and that is basically redirecting a console output to a Textbox, which isn't what I am looking for. I am looking to attach a stream to a Textbox, so that any writes to the stream automatically appear in the Textbox. – Intrepid Sep 12 '13 at 13:34
  • And you haven't considered just using Console.Write/Line() in your code at all? Very unclear why. Clearly that will go the console in console mode. And to a text box in GUI mode with the provided solution. – Hans Passant Sep 12 '13 at 13:42
  • 1
    I have answered my own question and posted a solution should anyone require something like this. – Intrepid Sep 13 '13 at 06:59
  • Did you managed to really solve this in the end? A person commented on your answer saying it wouldn't work. I have a very similar problem I am trying to solve. – eri0o Oct 01 '19 at 23:57

2 Answers2

2

I have managed to resolve this by creating a ConcurrentStreamWriter class that inherits StreamWriter and uses a ConcurrentQueue backed up by BackgroundWorker to process the queue's contents.

This is the solution I have come up with:

using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace Quest.Core.IO
{
    public class ConcurrentStreamWriter : StreamWriter
    {
        private ConcurrentQueue<String> _stringQueue = new ConcurrentQueue<String>();
        private Boolean _disposing;
        private RichTextBox _textBox;

        public ConcurrentStreamWriter( Stream stream )
            : base( stream )
        {
            CreateQueueListener();
        }

        public ConcurrentStreamWriter( Stream stream, RichTextBox textBox )
            : this( stream )
        {
            _textBox = textBox;
        }

        public override void WriteLine()
        {
            base.WriteLine();
            _stringQueue.Enqueue( Environment.NewLine );
        }

        public override void WriteLine( string value )
        {
            base.WriteLine( value );
            _stringQueue.Enqueue( String.Format( "{0}\n", value ) );
        }

        public override void Write( string value )
        {
            base.Write( value );
            _stringQueue.Enqueue( value );
        }

        protected override void Dispose( bool disposing )
        {
            base.Dispose( disposing );

            _disposing = disposing;
        }

        private void CreateQueueListener()
        {
            var bw = new BackgroundWorker();

            bw.DoWork += ( sender, args ) =>
            {
                while ( !_disposing )
                {
                    if ( _stringQueue.Count > 0 )
                    {
                        string value = string.Empty;
                        if ( _stringQueue.TryDequeue( out value ) )
                        {
                            if ( _textBox != null )
                            {
                                if ( _textBox.InvokeRequired )
                                {
                                    _textBox.Invoke( new Action( () =>
                                    {
                                        _textBox.AppendText( value );
                                        _textBox.ScrollToCaret();
                                    } ) );
                                }
                                else
                                {
                                    _textBox.AppendText( value );
                                    _textBox.ScrollToCaret();
                                }
                            }
                        }
                    }
                }
            };

            bw.RunWorkerAsync();

        }

    }
}
Intrepid
  • 2,781
  • 2
  • 29
  • 54
  • Ok, I got here from the [other question](http://stackoverflow.com/q/18786802/69809), so now I can see the whole code. Yes, this won't work, as I've already mentioned there. `MemoryStream` is not thread-safe for sharing between threads. You should ditch the `StreamWriter`, replace `MemoryStream` with a `ConcurrentQueue`, and use `Enqueue`/`Dequeue` to work with the FIFO buffer. – vgru Sep 13 '13 at 13:59
-1

There is nothing like this built into the framework. Instead of doing it the hard way, simply add a static helper method (available anywhere) that you will call whenever you want to output something:

public static void Output(string message)
{
    if ( IsConsoleApp() )
    {
        // process Console input and output
    }
    else
    {
        // process Windows input and output
    }
}

Then just have one line when you want output to show:

Utils.Output("Hello, World!");

To identify whether running as Console or not, you can use such code:

private static bool? IsConsole = null;
public static void Output(string message)
{
    if (IsConsole == null)
    {
        int width;
        try
        {
            width = Console.WindowWidth;
        }
        catch
        {
            width = 0;
        }
        IsConsole = (width > 0);
    }

    if (IsConsole.Value == true)
    {
        // process Console input and output
    }
    else
    {
        // process Windows input and output
    }
}

Not super elegant, but should work.

Shadow The GPT Wizard
  • 66,030
  • 26
  • 140
  • 208
  • The static method would then need to know whether the application is running as a console or Windows; that information is only available in a separate instance object containing the application's data. Can a static method access instance data? – Intrepid Sep 12 '13 at 13:14
  • @MikeClarke see my edit for a way to identify this without needing anything else. :) – Shadow The GPT Wizard Sep 12 '13 at 13:23
  • the only issue I can see is that the Windows portion of your code would not know which Form instance to use, which is why I wanted to use Streams in the first place. My application's data has an enum that I use to determine whether the app is to run as a console or Windows, so I'd much rather use that. It looks like I am going to have to rethink my design. – Intrepid Sep 12 '13 at 13:30
  • 1
    This depends how and where you store the form instance? – Shadow The GPT Wizard Sep 12 '13 at 13:32