Redirect I/O to a TextBoxWriter in .NET

A data stream is like a river where you set data adrift. You put data on the river and it floats along, waiting to be plucked out at some other location. As with streams in nature, you can move up or down the stream, see what’s there, and put in or take out things. Streams in .NET work that way too. In .NET, the console is represented by the stream class/metaphor. When you write a statement such as Console.WriteLine, the argument to WriteLine is put on the stream and it usually shows up in a command window.

Sometimes, though, you want basic output to go somewhere else besides the console (or DOS prompt). For example, System.Diagnostics sends Debug statements to the Output window in Visual Studio, and NUnit redirects Console.WriteLine statements to an internal window in its NUnit GUI. Well, you can redirect console output for your applications too, and this article shows you how. Specifically, you’ll learn how to redirect Console output streams to a TextBox instead of the command window.

Creating a Custom TextWriter

The System.Console class has an Out property. The Out property is an instance of a TextWriter. To redirect Console output statements, you have to define a class that inherits from System.IO.TextWriter and replaces the default value of Out to an instance of your custom class. After you define and create an instance of your custom TextWriter, you replace the value of Out by calling Console.SetOut.

After defining the custom TextWriter class, you need to define two things:

  1. A control that will be the new output locus
  2. An event handler that is fired when the Windows handle of the new output control is created. (You need the Windows handle because you can’t perform tasks such as sending text to a TextBox until that TextBox’s Windows handle is created.)

Listing 1 shows the custom TextWriter import statements, class header, and fields.

Listing 1: The Stub for the Custom TextWriter

Imports System.Windows.Forms
Imports System.Text

Public Class TextBoxWriter
   Inherits System.IO.TextWriter

   Private control As TextBoxBase
   Private Builder As StringBuilder

End Class

Implementing the constructor

You add the constructor to the code in Listing 1. The constructor accepts a TextBox as an argument to the constructor and wires up the HandleCreated event handler for the TextBox. Listing 2 is the code for passing a textbox control to the Sub New constructor and wiring up an event handler for the TextBox’s HandleCreated event. (The entire TextBoxWriter class is listed at the end of the article.)

Listing 2: Pass a TextBox Control and Wire Up an Event Handler

Public Sub New(ByVal control As TextBox)
   Me.control = control
   AddHandler control.HandleCreated, _
      New EventHandler(AddressOf OnHandleCreated)
End Sub

You don’t create the StringBuilder field in the constructor because you need it only temporarily: when data is sent to the TextBoxWriter and until the TextBox’s HandleCreated event fires.

Buffering I/O until the TextBox is created

You can send text to the Console by using Console.Write or Console.WriteLine before a TextBox can actually handle the text. So, you need to implement a buffering scheme that can store text sent to the TextBoxWriter until the TextBox’s handle is created. Once the handle is created, you can flush the buffer and forward any additional text directly to the control. Listing 3 contains a combination of Write and WriteLine methods and private subroutines for buffering text. The Public Write and WriteLine methods support basic output behaviors, and the private methods manage buffering text until the real output target is created. (You add the code in Listing 3 to the TextBoxWriter class from Listing 1.)

Listing 3: Public Write and WriteLine Methods, and Private Methods

Public Overrides Sub Write(ByVal ch As Char)
   Write(ch.ToString())
End Sub

Public Overrides Sub Write(ByVal s As String)
   If (control.IsHandleCreated) Then
      AppendText(s)
   Else
      BufferText(s)
   End If
End Sub

Public Overrides Sub WriteLine(ByVal s As String)
   Write(s + Environment.NewLine)
End Sub

Private Sub BufferText(ByVal s As String)
   If (Builder Is Nothing) Then
      Builder = New StringBuilder()
   End If

   Builder.Append(s)
End Sub

Private Sub AppendText(ByVal s As String)
   If (Builder Is Nothing = False) Then
      control.AppendText(Builder.ToString())
      Builder = Nothing
   End If
   control.AppendText(s)
End Sub

All Write invocations end up at Write(ByVal s as String). This Write statement checks the control to see whether the handle has been created. If the TextBox’s handle is created, it forwards the text to the control by using AppendText. If the control’s handle hasn’t been created yet, it buffers the text in the StringBuilder using the BufferText method. (The StringBuilder is created on demand, if needed, when BufferText is called.)

Knowing when the TextBox’s handle is created

The last bit of code is wired to the TextBox’s HandleCreated event property. Listing 4 demonstrates how you can flush the buffer and release the StringBuilder when the TextBox’s handle is created.

Listing 4: OnHandleCreated Responds When the TextBox’s Handle Is Created

Private Sub OnHandleCreated(ByVal sender As Object, _
   ByVal e As EventArgs)
   If (Builder Is Nothing = False) Then
      control.AppendText(Builder.ToString())
      Builder = Nothing
   End If
End Sub

Redirecting Console I/O

Listing 5 shows the complete code for the TextBoxWriter.

Listing 5: Complete Listing for TextBoxWriter

Imports System.Windows.Forms
Imports System.Text

Public Class TextBoxWriter
   Inherits System.IO.TextWriter

   Private control As TextBoxBase
   Private Builder As StringBuilder

   Public Sub New(ByVal control As TextBox)
      Me.control = control
      AddHandler control.HandleCreated, _
         New EventHandler(AddressOf OnHandleCreated)
   End Sub

   Public Overrides Sub Write(ByVal ch As Char)
      Write(ch.ToString())
   End Sub

   Public Overrides Sub Write(ByVal s As String)
      If (control.IsHandleCreated) Then
         AppendText(s)
      Else
         BufferText(s)
      End If
   End Sub

   Public Overrides Sub WriteLine(ByVal s As String)
      Write(s + Environment.NewLine)
   End Sub

   Private Sub BufferText(ByVal s As String)
      If (Builder Is Nothing) Then
         Builder = New StringBuilder()
      End If
      Builder.Append(s)
   End Sub

   Private Sub AppendText(ByVal s As String)
      If (Builder Is Nothing = False) Then
         control.AppendText(Builder.ToString())
         Builder = Nothing
      End If

      control.AppendText(s)
   End Sub

   Private Sub OnHandleCreated(ByVal sender As Object, _
      ByVal e As EventArgs)
      If (Builder Is Nothing = False) Then
         control.AppendText(Builder.ToString())
         Builder = Nothing
      End If
   End Sub

   Public Overrides ReadOnly Property Encoding() As System.Text.Encoding
      Get
         Return Encoding.Default
      End Get
   End Property
End Class

To use the TextBoxWriter, you need a TextBox control and you need to create an instance of the TextBoxWriter. You use the TextBox to initialize the TextBoxWriter, and you pass the TextBoxWriter to Console.SetOut to change the value of the Console.Out property. (See Listing 6 for the form that contains the TextBox and the code that redirects the Console output stream.)

Listing 6: The Form Containing the Textbox and the Redirect Code for the Console Output Stream

Public Class Form1
   Private writer As TextBoxWriter = Nothing

   Private Sub Form1_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
      writer = New TextBoxWriter(TextBox1)
      Console.SetOut(writer)
   End Sub

   Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
      Console.WriteLine("This is some redirected text")
   End Sub
End Class

The result of the code that redirects the Console output stream will look like Figure 1.

Figure 1: Output Sent to the Redirect Console

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his new book UML DeMystified from McGraw-Hill/Osborne. Paul is an architect for Tri-State Hospital Supply Corporation. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.

Copyright © 2006 by Paul T. Kimmel. All Rights Reserved.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read