实时将流重定向到文本框

3

我有一个有趣的难题,我的应用可以作为控制台应用程序或Windows窗体应用程序运行。

由于我不想在我的整个应用程序中编写大量如下的代码:

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

为了避免这种情况,我决定创建两种方法,可以传入TextReader和TextWriter实例,然后使用它们来处理输入和输出,例如:
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 );

为了在控制台窗口中显示一些文本,我只需要像这样做:

打印一些文本到控制台窗口可以使用以下方法:

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

文本会自动重定向到控制台。

现在,我的问题是如何为Windows应用程序实现类似的功能?我已经创建了一个包含只读文本框控件的表单,并希望将_outputStream的内容实时重定向到该文本框。

此外,我希望_inputStream包含另一个文本框控件的内容,以便我的应用程序可以从该流中读取,而不是直接从文本框中读取。

提前致谢。


这是一个合理的设计。框架中没有直接连接到文本框的流,但您可以很容易地实现自己的子类。而且可能有一些具有许多功能的第三方解决方案。 - Ben Voigt
可能是将控制台输出绑定到RichEdit的重复问题。 - Hans Passant
@HansPassant 我已经查看了您提供的链接,那基本上是将控制台输出重定向到一个文本框,这不是我要找的。我想要的是将流附加到文本框,以便任何写入流的内容都会自动显示在文本框中。 - Intrepid
你有没有考虑在代码中完全使用Console.Write/Line()呢?非常不清楚为什么不这样做。显然,这将在控制台模式下进入控制台,在GUI模式下则会使用提供的解决方案输入到文本框中。 - Hans Passant
1
我已经回答了自己的问题并发布了解决方案,如果有人需要类似的东西可以参考。 - Intrepid
你最终解决了这个问题吗?有人在你的答案下评论说它行不通。我也有一个非常类似的问题需要解决。 - eri0o
2个回答

2
我已经通过创建一个继承自StreamWriterConcurrentStreamWriter类,并使用由BackgroundWorker支持的ConcurrentQueue来处理队列内容,成功解决了这个问题。
以下是我想出的解决方案:
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();

        }

    }
}

好的,我从另一个问题来到这里,所以现在我可以看到整个代码。是的,这不起作用,正如我已经提到过的那样。MemoryStream在线程之间共享时不是线程安全的。你应该放弃StreamWriter,使用ConcurrentQueue<string>替换MemoryStream,并使用Enqueue/Dequeue来处理FIFO缓冲区。 - vgru

-1

框架中没有类似的功能。不必费力,只需添加一个静态帮助方法(可在任何地方使用),每当您想要输出内容时调用它即可:

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

当您想要输出显示时,只需有一行代码:

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

要确定程序是作为控制台运行还是其他方式运行,您可以使用以下代码:

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
    }
}

不是特别优雅,但应该能工作。


静态方法需要知道应用程序是作为控制台还是Windows运行;这些信息仅在包含应用程序数据的单独实例对象中可用。静态方法能够访问实例数据吗? - Intrepid
@MikeClarke,看我的编辑,有一种方法可以在不需要其他任何东西的情况下识别它。 :) - Shadow The Spring Wizard
我唯一能看到的问题是,你代码中的Windows部分不知道该使用哪个表单实例,这就是我想要先使用Streams的原因。我的应用程序数据有一个枚举,用于确定应用程序是要作为控制台还是Windows运行,因此我更愿意使用它。看起来我需要重新思考我的设计了。 - Intrepid
1
这取决于您如何以及在哪里存储表单实例? - Shadow The Spring Wizard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接