Another small class with nothing fancy or advanced, but that could spare time in not having to do it yourself ;-)
.net has a lot of nifty debugging features. For Runtime support a lot of convenient code exists in the System.Diagnostics namespace.
But.. if you're anything like me, you use the Console.Write and WriteLine functions when developing. (when sometimes I really should use the Debug.Write... functions, but find it too convenient to use Console)
Or.. other times when written for a console app, but now you want the same code to run in a windows app and still see the output.
In any case, .net has made it incredibly easy to redirect the standard output. No tutorial here on that subject, but still, had made a default output several times and decided that I could do with something generic.
Well, here it is. It's very simple code which does nothing more than redirect the Console output to a listbox. It runs asynchronously with the calling code, so it won't hold it up that much, but the code might be ahead of the output in fast output generating output files.
To start using, simply set:
Subro.ConsoleOutput.Redirect = true;
To stop, surprise surprise, set Redirect to false. By default the maximum number of lines kept (and displayed) is 250, but this can be changed by setting the ConsoleOutput.MaxLines property
namespace Subro
{
using System.Threading;
using System.IO;
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Text;
using System.ComponentModel;
public class ConsoleOutput :
Form{
static
volatile ConsoleOutput instance;
/// <summary>
/// Starts or stops the redirection of the console output to a ConsoleOutput form window
///
public static bool Redirect
{
get
{
return instance != null;
}
set
{
if (Redirect ==
value)
return;
WaitLock();
//instanceLock.AcquireReaderLock(timeout);
if (Redirect !=
value)
{
locked =
true;
if (
value)
{
Thread t = new Thread(new ThreadStart(show));
t.Start();
}
else
{
instance.Invoke(new ThreadStart(instance.Close));
}}
WaitLock();
} }
const int timeout = 5000;
volatile static bool locked;
//static ReaderWriterLock instanceLock = new ReaderWriterLock();
/// <summary>
/// Thread start to start showing the output form in its own thread
///
static void show()
{
new ConsoleOutput().ShowDialog();
}
static void WaitLock()
{
while (locked) { }
}
static volatile int max = 250;
public static int MaxLines
{
get {
return max; }
set{
max =
value;
if (instance !=
null)
instance.Invoke(new ThreadStart(instance.CheckMax));
}
}
static bool ShowTime = true;
public static bool ShowEntryTimes
{
get {
return ShowTime; }
set{
ShowTime =
value;
if (instance !=
null)
instance.Invalidate();
}
}
///
<summary>
/// The original output of the console window
///
TextWriter orgoutput;
protected override void OnClosing(
CancelEventArgs e)
{
if (orgoutput !=
null)
Console.SetOut(orgoutput);
if (stream !=
null)
stream.Dispose();
instance =
null;
locked =
false;
base.OnClosing(e);
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
instance = this;
stream = new CStream(this);
orgoutput = Console.Out;
Console.SetOut(stream);
locked = false;
}
protected override Size DefaultSize
{
get
{
return new Size(500, 300);
} }
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape) Close();
base.OnKeyDown(e);
}
CStream stream;
ConsoleBox lb = new ConsoleBox();
ConsoleOutput()
{
lb.Dock = DockStyle.Fill;
Controls.Add(lb);
Text = "Console output";
KeyPreview = true;
}
class
ConsoleBox :
ListBox{
public ConsoleBox()
{
BackColor = Color.Black;
ForeColor = Color.White;
brush = new SolidBrush(ForeColor);
DrawMode = DrawMode.OwnerDrawVariable;
}
SolidBrush brush;
static SolidBrush timeBrush =
new SolidBrush(
Color.Yellow);
static Font timefont =
new Font(
FontFamily.GenericSansSerif, 7);
static int timeHeight;
protected
override void OnDrawItem(
DrawItemEventArgs e)
{
if (e.Index == -1)
return;
Graphics g = e.Graphics;
e.DrawBackground();
ConsoleEntry ce = Items[e.Index]
as ConsoleEntry;
Rectangle rect = e.Bounds;
if (ShowTime)
{
g.DrawString(ce.GetCreationTime(), timefont, timeBrush, rect);
rect.Offset(0, timeHeight);
}
g.DrawString(ce.Value, e.Font, brush,rect);
}
protected
override void OnMeasureItem(
MeasureItemEventArgs e)
{
ConsoleEntry ce = Items[e.Index]
as ConsoleEntry;
SizeF size= e.Graphics.MeasureString(ce.Value, Font);
e.ItemHeight = (
int)size.Height;
if (ShowTime)
{
if (timeHeight == 0)
timeHeight = (
int)
e.Graphics.MeasureString(ce.GetCreationTime(), timefont).Height;
e.ItemHeight += timeHeight;
} }
}
void CheckMax()
{
stream.CheckMax();
}
class ConsoleEntry :
INotifyPropertyChanged{
public
readonly DateTime CreationTime =
DateTime.Now;
public string Value
{
get {
return value; }
set{
this.value =
value;
if (ValueChanged !=
null)
ValueChanged(this, EventArgs.Empty);
if (PropertyChanged !=
null)
PropertyChanged(this, pe);
} }
public event EventHandler ValueChanged;
public event PropertyChangedEventHandler PropertyChanged;
static PropertyChangedEventArgs pe = new PropertyChangedEventArgs("Value");
string value;
public ConsoleEntry(string Value)
{
this.value = Value;
}
public override string ToString()
{
return value;
}
public
string GetCreationTime()
{
return CreationTime.ToString("hh:mm:ss.ffff");
}
}
class CStream :
TextWriter{
ConsoleOutput owner;
ListBox lb;
public CStream(
ConsoleOutput owner)
{
this.owner = owner;
lb = owner.lb;
lb.DataSource = list;
lb.DisplayMember = "Value";
}
public override void Write(string value)
{
Write(value, false);
base.Write(value);
}
public override void WriteLine(string value)
{
Write(value, true);
base.WriteLine(value);
}
ConsoleEntry last;
BindingList<ConsoleEntry> list = new BindingList<ConsoleEntry>();
delegate void addDelegate(string value, bool end);
void Write(string value, bool end)
{
if (locked)
return;
try{
lb.BeginInvoke(new addDelegate(write), new object[] { value, end });
}
catch (
ObjectDisposedException)
{
}
catch (
InvalidOperationException)
{
lb.CreateControl();
Write(value, end);
}
}
int index;
void write(string value, bool end)
{
lock (list)
{
if (last ==
null)
{
last = new ConsoleEntry(value);
list.Add(last);
index = list.Count - 1;
}
else{
last.Value += value;
}
lb.SelectedIndex = index;
CheckMax();
if (end)
last = null;
} }
public void CheckMax()
{
if (list.Count <= max)
return;
lock (list)
while (list.Count > max)
{
list.RemoveAt(0);
index--;
}
//list.RemoveRange(0, list.Count - max); }
public override Encoding Encoding
{
get { return Encoding.Unicode; }
} }
}
}
. . .
26-6-6
Understandably, comments have arisen and might still arise about the very ugly thread locking procedure. Well, let's just keep it at that it got it reasons and that it is hardly used when running. Please see the comments below for further explanation : http://blogs.vbcity.com/hotdog/archive/2006/07/10/6089.aspx#6120
But... please feel free to experiment for other small impact solutions, but when testing elaborately I hope you agree that the normal locking mechanisms don't do the job in this case. To be honest, I didn't really look any further because the lock is needed only when starting (or stopping) the redirect, which takes a few milliseconds and will not be noticed. So experimenting on a large scale could ultimately be a waste of your time :p ;-)