Project Description
This project shows how to intercept the console text and redirect it to other streams and WPF UI elements such as a Textbox, while still writing to the standard console. This project presents classes to perform this task, and gives an example debug console window.


debugconsole.png


Multicast Redirect TextWriter
First we inherit a TextWriter and overriding its write(...) and writeline(...) methods. I inherit from the String writer to save myself the trouble of implementing all the TextWriter methods. Feel free to override any another additional methods you may need.

Some key things I will point is the use of generics to make the code more concise. The generic method allows for you to easily modify the behavior or set of methods with the same behavior, but different signatures.

     public class RedirectWriter : StringWriter
    {
       
        public Action<String> OnWrite;

        private void WriteGeneric<T>(T value) {  if (OnWrite != null) OnWrite(value.ToString()); }


        public override void Write(char value) { WriteGeneric<char>(value); }
        public override void Write(string value) { WriteGeneric<string>(value); }
        public override void Write(bool value) { WriteGeneric<bool>(value); }
        public override void Write(int value) { WriteGeneric<int>(value); }
        public override void Write(double value) { WriteGeneric<double>(value); }
        public override void Write(long value) { WriteGeneric<long>(value); }

        private void WriteLineGeneric<T>(T value) { if (OnWrite != null) OnWrite(value.ToString() + "\n"); }
        public override void WriteLine(char value) { WriteLineGeneric<char>(value); }
        public override void WriteLine(string value) { WriteLineGeneric<string>(value); }
        public override void WriteLine(bool value) { WriteLineGeneric<bool>(value); }
        public override void WriteLine(int value) { WriteLineGeneric<int>(value); }
        public override void WriteLine(double value) { WriteLineGeneric<double>(value); }
        public override void WriteLine(long value) { WriteLineGeneric<long>(value); }

        public override void Write(char[] buffer, int index, int count)
        {           
            base.Write(buffer,index,count);
            char[] buffer2 = new char[count]; //Ensures large buffers are not a problem
            for (int i = 0; i < count; i++) buffer2[i] = buffer[index + i];
            WriteGeneric<char[]>(buffer2);
        }

        public override void WriteLine(char[] buffer, int index, int count)
        {
            base.Write(buffer, index, count);
            char[] buffer2 = new char[count]; //Ensures large buffers are not a problem
            for (int i = 0; i < count; i++) buffer2[i] = buffer[index + i];
            WriteLineGeneric<char[]>(buffer2);
        }
    }



Console Redirect
Next we create a class for redirecting the console stream to all registered delegates while still writing to the initial console.

    public class ConsoleRedirectWriter : RedirectWriter
    {
        TextWriter consoleTextWriter; //keeps Visual Studio console in scope.

        public ConsoleRedirectWriter()
        {
            consoleTextWriter = Console.Out;
            this.OnWrite += delegate(string text) { consoleTextWriter.Write(text); };
            Console.SetOut(this);
        }

        public void Release()
        {
            Console.SetOut(consoleTextWriter);
        }
    }


Example Application
Next we declare this in your window, and register our UIelements or other streams. Its advisable to use the ConsoleRedirectWriter like a singleton. Multiple declarations have not been tested.

    public partial class WindowDebugConsole : Window
    {
        
        ConsoleRedirectWriter consoleRedirectWriter = new ConsoleRedirectWriter(); 
        String LastConsoleString;

        public WindowDebugConsole()
        {         
            InitializeComponent();
            this.Closed += delegate(Object o, EventArgs e) { consoleRedirectWriter.Release(); };  //sets releases console when window closes.
        }

        private void textBoxDebug_Initialized(object sender, EventArgs e)
        {
            // Use this for thread safe objects or UIElements in a single thread program
            consoleRedirectWriter.OnWrite += delegate(string value) { LastConsoleString = value; };
            
            // Multithread operation - Use the dispatcher to write to WPF UIElements if there is more than 1 thread.
            consoleRedirectWriter.OnWrite +=  Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                (Action<string>) delegate(string value){ textBoxDebug.AppendText(value); textBoxDebug.ScrollToEnd();}, text );
        }
    
    }

<Window x:Class="Application.WindowDebugConsole"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Debug Console" Height="300" Width="300" WindowStyle="ToolWindow">
    <Grid>
        <TextBox Name="textBoxDebug" ScrollViewer.VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" Initialized="textBoxDebug_Initialized" />
    </Grid>
</Window>

Your console window is now ready to open!

I like to bind mine to a debug MenuItem as shown below.

         private void menuItemDebugConsole_Checked(object sender, RoutedEventArgs e)
        {
            UtilityPattern.MultithreadWindowLaunchPattern(this, windowDebugConsole, delegate() { menuItemDebugConsole.IsChecked = false; });
        }

        private void menuItemDebugConsole_Unchecked(object sender, RoutedEventArgs e)
        {
            if (windowDebugConsole != null) windowDebugConsole.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate() { windowDebugConsole.Close(); });       
        }

        public static void MultithreadWindowLaunchPattern(Window parent, Window child, Action OnChildCloseParentAction )
        {
            Thread thread = new Thread( () => {
                child = new WindowDebugConsole();
                child.Topmost = true;

                child.Closed += (sender2, e2) =>
                {
                    child.Dispatcher.InvokeShutdown();
                    parent.Dispatcher.BeginInvoke(DispatcherPriority.Normal, OnChildCloseParentAction);
                };      
                child.Show();                
                System.Windows.Threading.Dispatcher.Run();
            });
            
            parent.Closed += delegate(object a, EventArgs ev) { child.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate() { child.Close(); });};
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }
    }



And there you have it, a method for redirecting the console, and a WPF example of its application.

Last edited Oct 2, 2010 at 4:37 AM by gsonnenf, version 15