Click here to Skip to main content
15,892,161 members
Articles / Programming Languages / C#
Article

Debug Console Window

Rate me:
Please Sign up or sign in to vote.
4.88/5 (15 votes)
1 Sep 20022 min read 176.9K   3K   45   21
A console window class useful for debugging, reporting events during runtime and saving logs on disk.

Image 1

Introduction

I usually send various debug messages to .txt files or the standard console, but I somehow got tired of this DOS feeling... Interactive functions like saving the whole output to a file with one click or easily clearing the list are not available as well. So I started writing this dialog console. The DebugConsole class is a singleton that has the Form class as member, this seemed the best way in C# to have this class acting like a global variable.

The first version didn't have any support for the listening code of .NET and Richard D. proposed to derive this class from System.Diagnostics.TraceListener, very good idea indeed as it simplified the calls too, thanks Richard. You just need to include the System.Diagnostics namespace and call the DebugConsole.Instance.Init() method. The first parameter tells if you want to set the debug listener (true) or the trace listener (false); the 2nd parameter concerns the carriage return for WriteLine(), if it is set to true, the message sent to WriteLine will use a new line instead of being added to the current buffer. This happens when you use the Write() function. WriteLine() and Write() are the only functions with the override keyword.

C#
using System.Diagnostics;

[STAThread]
static void Main() 
{
    #if (DEBUG)
    // debug mode

        DebugConsole.Instance.Init(true,true);
    #else
    // release mode
       DebugConsole.Instance.Init(false,true);
    #endif
    
    Application.Run(new Form1());
}

void MyFunction()
{
   float f=3.1415f;
   Debug.WriteLine("Output will only appear in Debug Mode");
   Trace.WriteLine("Output will appear in both Debug 
                   and Release mode" +   f.ToString());

   Debug.Write("1");
   Debug.Write("2");
}

This will immediately write the strings to the console and send them to the listeners (strings that you can visualize with an external tool like DebugView). The console is resizable now and I added an 'always on top' option.

The presence of the singleton also means you don't have to declare the object anywhere, these steps are automatically done by the class when you call Init(). The window is placed at the top-left corner of the screen. You can change the color of the ListView in the designer.

The timestamp uses the DateTime class of .NET, you can use this class to add a 'date' button, milliseconds...Feel free to improve it :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
France France
Student at the EPFL (Lausanne/Switzerland) in informatics. I have been coding since 1996 and I am member of the Swiss demogroup Calodox.

Comments and Discussions

 
GeneralMake it thread dafe by add/change the following Pin
henryYYY10-Oct-07 12:04
henryYYY10-Oct-07 12:04 
NewsDo it Threadsafe with BeginInvoke.... Pin
THaala17-Apr-07 10:30
THaala17-Apr-07 10:30 
GeneralNext Gen VS 2005 Debug Console. Pin
riscy11-Aug-06 7:41
riscy11-Aug-06 7:41 
QuestionRe: Next Gen VS 2005 Debug Console. Pin
riscy12-Aug-06 23:33
riscy12-Aug-06 23:33 
AnswerRe: Next Gen VS 2005 Debug Console. Pin
Wolfgang G. Schmidt5-Feb-07 20:06
Wolfgang G. Schmidt5-Feb-07 20:06 
GeneralRe: Next Gen VS 2005 Debug Console. Pin
riscy5-Feb-07 20:34
riscy5-Feb-07 20:34 
QuestionRe: Next Gen VS 2005 Debug Console. Pin
Marcus Deecke5-Jul-07 13:35
Marcus Deecke5-Jul-07 13:35 
GeneralLatest Build Pin
twesterd14-Apr-06 9:38
twesterd14-Apr-06 9:38 
GeneralThanks! Pin
hannahb4-Jul-05 7:38
hannahb4-Jul-05 7:38 
GeneralThreading problems Pin
stevz328-Nov-03 2:29
stevz328-Nov-03 2:29 
GeneralVery helpful Pin
Mark Focas26-Nov-03 12:11
Mark Focas26-Nov-03 12:11 
GeneralSingleton & Namespace query Pin
Paul Evans14-May-03 3:23
Paul Evans14-May-03 3:23 
GeneralBIG MSG - My Changes to Your code Pin
Paul Evans14-May-03 5:26
Paul Evans14-May-03 5:26 
I couldn't see a way of e-mailing you, so here's some changes I made, incase u are interested.

Thanks for this great starting point!

Paul



// DEBUG CONSOLE EXAMPLE v1.11
// Code by Kvin Drapel - 2002
// http://www.calodox.org
//
// Thanks to Richard D for the TraceListener idea
//
// Do what you want with this source.
//


//You must have TRACE defined to enable Tracing
#define TRACE

//Defined to allow conditional inclusion depending on availablilty of this file
#if (!(TRACELISTENER))
#define TRACELISTENER
#endif

namespace PDE.Debug
{
	using System;
	using System.Drawing;
	using System.Collections;
	using System.ComponentModel;
	using System.Windows.Forms;
	using System.IO;
	using System.Text;
	using System.Diagnostics;

	
	/// <summary>Implementation of Debug Console</summary>
	/// <remarks>	
	/// <para>
	/// Implemented as Singleton
	/// </para>	
	/// <para>
	/// Authors
	/// </para>	
	/// 
	/// <list type="bullet">
	/// 	
	/// <item>
	/// <term>Edited by</term>
	/// <description>Paul Evans</description>
	/// </item>
	/// 
	/// <item>
	/// <term>Edited by</term>
	/// <description>Richard D </description>
	/// </item>
	/// 
	/// <item>
	/// <term>Created by</term>
	/// <description>Kvin Drapel</description>
	/// </item>
	/// 
	/// </list>
	/// 
	/// <br></br>
	/// 	
	/// <para>  Version history of this file 
	/// </para>
	/// 
	/// <list type="bullet">
	/// <item>
	/// <term> 14May03 - PDE </term>
	/// <description>
	/// Editing, commenting, etc
	/// </description>
	/// </item>
	/// </list>
	/// 
	/// <br></br> 	
	/// 
	/// </remarks>		
	sealed class DebugConsole : TraceListener
	{
		///<summary>
		/// Singleton implementation - only entry point outside class
		///</summary>
		public static readonly DebugConsole Instance = 	new DebugConsole();


		///<summary>
		/// Initialise the debug form variable
		///</summary>
		private DebugConsoleWrapper DebugForm = new DebugConsoleWrapper();
		
		 
		///<summary>
		/// If this parameter is set to true, a call to WriteLine will always create a new row
		/// (if false, it may be appended to the current buffer created with some Write calls).
		/// Set by call to Init.
		///</summary>
		private bool UseCrWl = true;
		
		
		///<summary>Set if init'ed</summary>
		private bool m_bInit = false;


		///<summary>
		/// Constructor private to keep with Singleton pattern
		///</summary>
		private DebugConsole() 
		{
			//PDE 14May03 - Moved to Init so constructor is more lightweight
			//DebugForm.Show();
		}


		///<summary>
		/// Initalise the DebugConsole.
		///</summary>
		///<param name="UseDebugOutput">Added to Debug.Listeners if true, Trace.Listeners if false.</param>
		///<param name="UseCrForWriteLine">If this parameter is set to true, a call to WriteLine will always create a new row
		/// (if false, it may be appended to the current buffer created with some Write calls).
		/// </param>
		public void Init(bool UseDebugOutput, bool UseCrForWriteLine)
		{
			if (UseDebugOutput==true)
				Debug.Listeners.Add(this);
			else
				Trace.Listeners.Add(this);

			this.UseCrWl = UseCrForWriteLine;
			
			DebugForm.Show();
			
			this.m_bInit=true;
			
			Trace.WriteLine("--- DEBUG CONSOLE LOG INITIALISED ---");			
		}
		
		
		///<summary>
		/// Initalise the DebugConsole.
		///</summary>
		/// <remarks>
		/// <para>
		/// UseCrForWriteLine is set to true in this version.
		/// </para>
		/// </remarks>
		///<param name="UseDebugOutput">Added to Debug.Listeners if true, Trace.Listeners if false.</param>		
		/// (if false, it may be appended to the current buffer created with some Write calls).
		/// </param>
		public void Init(bool UseDebugOutput)
		{
			Init(UseDebugOutput, true);
		}
		
		
		///<summary>
		/// Constructor private to keep with Singleton pattern
		///</summary>
		/// <remarks>
		/// <para>
		/// UseCrForWriteLine is set to true in this version.
		/// </para>
		/// <para>
		/// UseDebugOutput determined using compiler macro testing for DEBUG being defined.
		/// </para>
		/// </remarks>		
		public void Init()
		{
			#if (DEBUG)
				// debug mode (Debug)
				Init(true,true);
				Trace.WriteLine("--- (Initialised using defaults (Debug mode, writing new lines) ---");
			#else
				// release mode (Trace)
				Init(false,true);
				Trace.WriteLine("--- (Initialised using defaults (Trace mode, writing new lines) ---");
			#endif						
		}


		///<summary>
		/// Append message to debug buffer without performing a new line.
		///</summary>
		///<param name="message">Message to display</param>
		override public void Write(string message) 
		{   
			//PDE: Check to ensure everything is initalised first
			if (!m_bInit)
			{
				Init();
			}
			
			DebugForm.Buffer.Append(message);
			DebugForm.UpdateCurrentRow(false);
		}


		///<summary>
		/// Append message to debug buffer and perform a new line.
		///</summary>
		/// <remarks>
		/// <para >
		/// TODO: PDE: Check through buffer update strategy and optimise where appropriate
		/// </para>
		/// </remarks>
		///<param name="message">Message to display</param>
		override public void WriteLine(string message) 
		{   
			//PDE: Check to ensure everything is initalised first
			if (!m_bInit)
			{
				Init();
			}

			if (this.UseCrWl==true) 
			{
				DebugForm.CreateEventRow();
				DebugForm.Buffer=new StringBuilder();
			}

			DebugForm.Buffer.Append(message); 
			DebugForm.UpdateCurrentRow(true);
			
			//-----
			//PDE: Added length = 0 instead of creating a new StringBuilder to keep memory wastage down...
			DebugForm.Buffer.Length = 0;
			//DebugForm.Buffer = new StringBuilder(); 
			//-----
		}
		
		
		
		/// <summary>
		/// Implementation of Debug Console Form (Window). 
		/// </summary>		
		/// <remarks>	
		/// <para>
		/// PDE 14May03 - Moved inside class.
		/// </para>	
		/// <para>
		/// Authors
		/// </para>	
		/// 
		/// <list type="bullet">
		/// 	
		/// <item>
		/// <term>Edited by</term>
		/// <description>Paul Evans</description>
		/// </item>
		/// 			
		/// <item>
		/// <term>Created by</term>
		/// <description>Kvin Drapel</description>
		/// </item>
		/// 
		/// </list>
		/// 
		/// <br></br>
		/// 	
		/// <para>  Version history of this file 
		/// </para>
		/// 
		/// <list type="bullet">
		/// <item>
		/// <term> 14May03 - PDE </term>
		/// <description>
		/// Editing, commenting, etc
		/// </description>
		/// </item>
		/// </list>
		/// 
		/// <br></br> 	
		/// 
		/// </remarks>		
		private class DebugConsoleWrapper : System.Windows.Forms.Form
		{
			private System.Windows.Forms.Button BtnSave;
			private System.Windows.Forms.Button BtnClear;
			private System.Windows.Forms.SaveFileDialog SaveFileDlg;
			private System.Windows.Forms.CheckBox CheckScroll;
			private System.Diagnostics.DefaultTraceListener Tracer = new System.Diagnostics.DefaultTraceListener();
			private System.Windows.Forms.ColumnHeader Col1;
			private System.Windows.Forms.ColumnHeader Col2;
			private System.Windows.Forms.ListView OutputView;
			private System.Windows.Forms.CheckBox CheckTop;
			private System.Windows.Forms.Panel panel2;
			private System.Windows.Forms.ColumnHeader Col3;
			private ListViewItem.ListViewSubItem CurrentMsgItem = null;
			private int EventCounter=0;
			public StringBuilder Buffer = new StringBuilder();
	
			/// <summary>
			/// Required designer variable.
			/// </summary>
			/// 
			private System.ComponentModel.Container components = null;
			
			
			/// <summary>
			/// Initalise components on construction
			/// </summary>
			public DebugConsoleWrapper()
			{
				InitializeComponent();
			}
			
	
			/// <summary>
			/// Clean up any resources being used.
			/// </summary>
			protected override void Dispose( bool disposing )
			{
				if( disposing )
				{
					if(components != null)
					{
						components.Dispose();
					}
				}
				base.Dispose( disposing );
			}
			
	
			#region Windows Form Designer generated code
			/// <summary>
			/// Required method for Designer support - do not modify
			/// the contents of this method with the code editor.
			/// </summary>
			private void InitializeComponent()
			{
				this.BtnSave = new System.Windows.Forms.Button();
				this.BtnClear = new System.Windows.Forms.Button();
				this.SaveFileDlg = new System.Windows.Forms.SaveFileDialog();
				this.CheckScroll = new System.Windows.Forms.CheckBox();
				this.OutputView = new System.Windows.Forms.ListView();
				this.Col1 = new System.Windows.Forms.ColumnHeader();
				this.Col2 = new System.Windows.Forms.ColumnHeader();
				this.Col3 = new System.Windows.Forms.ColumnHeader();
				this.CheckTop = new System.Windows.Forms.CheckBox();
				this.panel2 = new System.Windows.Forms.Panel();
				this.panel2.SuspendLayout();
				this.SuspendLayout();
								
				// 
				// BtnSave
				// 
				this.BtnSave.Location = new System.Drawing.Point(8, 16);
				this.BtnSave.Name = "BtnSave";
				this.BtnSave.Size = new System.Drawing.Size(64, 24);
				this.BtnSave.TabIndex = 8;
				this.BtnSave.Text = "Save";
				this.BtnSave.Click += new System.EventHandler(this.BtnSave_Click);
				// 
				// BtnClear
				// 
				this.BtnClear.Location = new System.Drawing.Point(80, 16);
				this.BtnClear.Name = "BtnClear";
				this.BtnClear.Size = new System.Drawing.Size(64, 24);
				this.BtnClear.TabIndex = 8;
				this.BtnClear.Text = "Clear";
				this.BtnClear.Click += new System.EventHandler(this.BtnClear_Click);
				// 
				// CheckScroll
				// 
				this.CheckScroll.Checked = true;
				this.CheckScroll.CheckState = System.Windows.Forms.CheckState.Checked;
				this.CheckScroll.Location = new System.Drawing.Point(152, 16);
				this.CheckScroll.Name = "CheckScroll";
				this.CheckScroll.Size = new System.Drawing.Size(80, 16);
				this.CheckScroll.TabIndex = 8;
				this.CheckScroll.Text = "autoscroll";
				this.CheckScroll.CheckedChanged += new System.EventHandler(this.CheckScroll_CheckedChanged);
				// 
				// OutputView
				// 
				this.OutputView.AutoArrange = false;
				this.OutputView.BackColor = System.Drawing.Color.MediumAquamarine;
				this.OutputView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
																							 this.Col1,
																							 this.Col2,
																							 this.Col3});
				this.OutputView.Dock = System.Windows.Forms.DockStyle.Fill;
				this.OutputView.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
				this.OutputView.ForeColor = System.Drawing.Color.Black;
				this.OutputView.Name = "OutputView";
				this.OutputView.Size = new System.Drawing.Size(400, 200);
				this.OutputView.TabIndex = 7;
				this.OutputView.View = System.Windows.Forms.View.Details;
				// 
				// Col1
				// 
				this.Col1.Text = "#";
				this.Col1.Width = 40;
				//this.Col1.Width = 30;
				// 
				// Col2
				// 
				this.Col2.Text = "Time";
				this.Col2.Width = 100;
				//this.Col2.Width = 70;
				// 
				// Col3
				// 
				this.Col3.Text = "Message";
				this.Col3.Width = 400;
				//this.Col3.Width = 270;
				// 
				// CheckTop
				// 
				this.CheckTop.Location = new System.Drawing.Point(240, 16);
				this.CheckTop.Name = "CheckTop";
				this.CheckTop.Size = new System.Drawing.Size(96, 16);
				this.CheckTop.TabIndex = 8;
				this.CheckTop.Text = "always on top";
				this.CheckTop.CheckedChanged += new System.EventHandler(this.CheckTop_CheckedChanged);
				// 
				// panel2
				// 
				this.panel2.Controls.AddRange(new System.Windows.Forms.Control[] {
																					 this.BtnSave,
																					 this.BtnClear,
																					 this.CheckScroll,
																					 this.CheckTop});
				this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
				this.panel2.Location = new System.Drawing.Point(0, 200);
				this.panel2.Name = "panel2";
				this.panel2.Size = new System.Drawing.Size(400, 48);
				this.panel2.TabIndex = 8;
				// 
				// DebugConsoleWrapper
				// 
				this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
				this.ClientSize = new System.Drawing.Size(400, 248);
				this.Controls.AddRange(new System.Windows.Forms.Control[] {
																			  this.OutputView,
																			  this.panel2});
				this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
				//this.MinimumSize = new System.Drawing.Size(390, 160);
				this.MinimumSize = new System.Drawing.Size(450, 160);
				this.Name = "DebugConsoleWrapper";
				this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
				this.Text = "Debug Console";
				
				
				this.panel2.ResumeLayout(false);
				this.ResumeLayout(false);
	
			}
			#endregion
	
	
			
			/// <summary>
			/// Creates a new list view item to display the event from Write / WriteLine.
			/// </summary>
			public void CreateEventRow()
			{
				DateTime d=DateTime.Now;
	
				// create a ListView item/subitems : [event nb] - [time] - [empty string]
				string msg1 = (++EventCounter).ToString();
				string msg2 = d.ToLongTimeString();
				ListViewItem elem = new ListViewItem(msg1);
				elem.SubItems.Add(msg2);
				elem.SubItems.Add("");
				this.OutputView.Items.Add(elem);
	
				// we save the message item for incoming text updates
				CurrentMsgItem=elem.SubItems[2];
			}
	
	
			/// <summary>
			/// Copies the debug buffer into the current list view item.  
			/// </summary>
			/// <param name="CreateRowNextTime">Cause creation of new line creation on next call to Write / WriteLine.</param>
			public void UpdateCurrentRow(bool CreateRowNextTime)
			{
				if (CurrentMsgItem==null) CreateEventRow();
				//PDE: Copy current buffer into the current listview item (which will have already been added to the list view)
				CurrentMsgItem.Text=Buffer.ToString();
	
				// if null, a new row will be created next time this function is called
				//PDE: As there is a reference to this object in the ListView control, this nullification is safe.
				if (CreateRowNextTime==true) CurrentMsgItem=null;
	
				// this is the autoscroll, move to the last element available in the ListView
				if (this.CheckScroll.CheckState == CheckState.Checked)
				{
					this.OutputView.EnsureVisible(this.OutputView.Items.Count-1);
				}
			}
			
			
			/// <summary>
			/// Offer option to save file on click
			/// </summary>
			/// <param name="sender">Object generating event.</param>
			/// <param name="e">Arguments generated by event.</param>
			private void BtnSave_Click(object sender, System.EventArgs e)
			{
				this.SaveFileDlg.Filter="Text file (*.txt)|*.txt|All files (*.*)|*.*" ;
				this.SaveFileDlg.FileName="log.txt";
				this.SaveFileDlg.ShowDialog();
	
				FileInfo  fileInfo = new FileInfo(SaveFileDlg.FileName);
	
				// create a new textfile and export all lines
				StreamWriter s = fileInfo.CreateText();
				for (int i=0;i<this.OutputView.Items.Count;i++)
				{
					StringBuilder sb=new StringBuilder();
					sb.Append(this.OutputView.Items[i].SubItems[0].Text);
					sb.Append("\t");
					sb.Append(this.OutputView.Items[i].SubItems[1].Text);
					sb.Append("\t");
					sb.Append(this.OutputView.Items[i].SubItems[2].Text);
					s.WriteLine(sb.ToString());
				}
	
				s.Close();
			}
	
	
			/// <summary>
			/// Clear contents on click
			/// </summary>
			/// <param name="sender">Object generating event.</param>
			/// <param name="e">Arguments generated by event.</param>
			private void BtnClear_Click(object sender, System.EventArgs e)
			{
				//-----
				//PDE: Changed order of below block slightly, and set StringBuilder Length to 0 rather then expense of creating new instance
				this.EventCounter=0;
				this.CurrentMsgItem=null;
				this.OutputView.Items.Clear();				
				//this.Buffer = new StringBuilder();
				this.Buffer.Length = 0;
				//-----
			}
	
	
			/// <summary>
			/// Always ontop checkbox status change
			/// </summary>
			/// <param name="sender">Object generating event.</param>
			/// <param name="e">Arguments generated by event.</param>
			private void CheckTop_CheckedChanged(object sender, System.EventArgs e)
			{
				if (this.CheckTop.CheckState == CheckState.Checked) 
					this.TopMost = true;
				else
					this.TopMost = false;
			}
	
	
			/// <summary>
			/// Scroll visible checkbox status change
			/// </summary>
			/// <param name="sender">Object generating event.</param>
			/// <param name="e">Arguments generated by event.</param>
			private void CheckScroll_CheckedChanged(object sender, System.EventArgs e)
			{
				if (this.CheckScroll.CheckState == CheckState.Checked)
					this.OutputView.EnsureVisible(this.OutputView.Items.Count-1);
			}
	
		}
	}	
}
 

/**********************************
Paul Evans, Dorset, UK.
Personal Homepage "EnjoySoftware" @ 
http://www.enjoysoftware.co.uk/
**********************************/

GeneralDebugConsole.Write Pin
Richard Deeming1-Sep-02 23:11
mveRichard Deeming1-Sep-02 23:11 
GeneralRe: DebugConsole.Write Pin
dake / calodox2-Sep-02 10:58
dake / calodox2-Sep-02 10:58 
GeneralRe: DebugConsole.Write Pin
Richard Deeming2-Sep-02 23:15
mveRichard Deeming2-Sep-02 23:15 
GeneralOutputDebugString Pin
Todd Smith1-Sep-02 5:15
Todd Smith1-Sep-02 5:15 
GeneralRe: OutputDebugString Pin
Joseph Dempsey1-Sep-02 6:27
Joseph Dempsey1-Sep-02 6:27 
GeneralRe: OutputDebugString Pin
dake / calodox1-Sep-02 8:30
dake / calodox1-Sep-02 8:30 
GeneralRe: OutputDebugString Pin
szurgot2-Sep-02 12:41
szurgot2-Sep-02 12:41 
GeneralRe: OutputDebugString Pin
dake / calodox3-Sep-02 10:04
dake / calodox3-Sep-02 10:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.