/Home /Archive /Syndicate /Blog /Support /About /Contact  
All Visual Basic Feeds in one place!





Code CopyHideScrollFull
using System;
using
System.Collections;
using
System.Collections.Generic;
using
System.Drawing;
using
System.Text;
using
System.Windows.Forms;
using
System.ComponentModel;
using
System.IO;
using
Subro.Exceptions;

namespace
Subro.Interaction
{
/// <summary>
///
A single feedback item. A feedback item can provide information
///
on status, progress and datetimes of execution
///
</summary>
public
class Feedback
{
public Feedback(string Message):this()
{
this.message = Message;
}
public
Feedback()
{
this.lastupdate = this.DateTime;
}
///
<summary>
///
The time the feedback object was created
///
</summary>
public
readonly DateTime DateTime = DateTime.Now;
DateTime
lastupdate;
///
<summary>
///
The last time the Feedback was updated (for example by updating the progress)
///
</summary>
public
DateTime LastUpdate
{
get { return lastupdate; }
}
private void SetLastUpdate()
{
lastupdate = DateTime.Now;
}
/// <summary>
///
The main information of this feedback
///
</summary>
string
message;
public
string Message
{
get { return message; }
set

{
message = value;
NotifyUpdate();
}
}
int progress;
///
<summary>
///
The progress of this item. The total progress is in relation
///
to the the value set at <see cref="ProgressTarget"/>
///
<seealso cref="ProgressTarget"/>
///
<see cref="ProgressPercentage"/>
///
</summary>            
public
int Progress
{
get { return progress; }
set

{
progress = value;
IsProgressUpdate = true;
NotifyUpdate();
}
}
public event EventHandler Updated;
public void NotifyUpdate()
{
SetLastUpdate();
if
(Updated != null)
Updated(this, EventArgs.Empty);
}
int progresstarget = 100;
bool
progressinpercentage = true;
///
<summary>
///
Normal target of process is 100 as in 100 percent. Whatever value this
///
is set to, progress relates to it.
///
For example: if looping through a number of lines,
///
you can set this target to the number of lines and <see cref="Progress"/> to
///
the line being processed
///
</summary>
public
int ProgressTarget
{
get { return progresstarget; }
set

{
progresstarget = value;
progressinpercentage = false;
NotifyUpdate();
}
}
/// <summary>
///
The relative progress of this item.
///
</summary>
public
float ProgressPercentage
{
get
{
if (!IsProgressUpdate) return 1;
return
(float)progress / progresstarget;
}
}
public string GetProgressString()
{
if (progressinpercentage)
return ProgressPercentage.ToString("###%");
return progress.ToString() + "/" + progresstarget.ToString();
}
/// <summary>
///
Increase the <see cref="Progress"/> by one
///
</summary>
public
void Increase()
{
Progress++;
}
public void ShowAlive()
{
IsAliveIndicator = true;
}
public
bool IsProgressUpdate = false;
public
int alivecount;
public
int AliveCount { get { return alivecount; } }
public
bool IsAliveIndicator
{
get { return alivecount > 0; }
set

{
if (value)
{
if (++alivecount == 0)
alivecount++;
NotifyUpdate();
}
else
alivecount = 0;
}
}
public
override string ToString()
{
return string.Format("{0}\t{1}", DateTime, message);
}
/// <summary>
///
The level, this can be a custom level or set with <see cref="FeedbackLevel"/>.
///
0 = normal  negative value is not so important, positive value is more important
///
</summary>
public
int Level = (int)FeedBackLevel.Normal;
public
FeedBackLevel FeedbackLevel
{
get
{
try
{
return (FeedBackLevel)Level;
}
catch

{
return FeedBackLevel.Custom;
}
}
set

{
Level = (int)value;
}
}
public bool IsNormalLevel { get { return Level == (int)FeedBackLevel.Normal; } }

public virtual bool CanShowExtraInfo
{
get { return HasChildren; }
}
public
void ShowExtraInfo()
{
ShowExtraInfo(null);
}
public
virtual void ShowExtraInfo(IWin32Window Owner)
{
children.Show(Owner);
}
protected
virtual void AppendExtraInfo(AppendInfoSettings info)
{
children.AppendFeedbackText(info);
}
public virtual FeedbackItemPainter GetPainter(FeedbackItemInfo info)
{
return new FeedbackItemPainter(info);
}
internal void AppendInfo(AppendInfoSettings info,int RowNumber)
{
var sb = info.StringBuilder;
sb.Append("<DIV>");
bool
bold = Level > 0, smaller = Level < 0;
if
(bold) sb.Append("<B>");
else
if (smaller) sb.Append("<span style='font-size:smaller'>");
sb.Append("<span style='font-size:smaller;font-style:italic;'>").Append(DateTime)
.Append("</span>");
if (IsProgressUpdate)
{
int p = (int)(ProgressPercentage * 100);
sb.Append(@"<span style=""border:'1 solid black';width:75px;margin-left:10;font-size:smaller"">")
.Append("<span style='position:absolute;width:")
.Append(p)
.Append("%;background-color:green'></span>")
.Append("<span style='position:relative;width:100%'>")
.Append(GetProgressString()).Append("</span>")
.Append("</span>");
}
sb.Append("<span style='margin-left:10'>").Append(message)
.Append("</span>");
if (CanShowExtraInfo)
{
string ExtraInfoAnchor = "ExtraInfo" + RowNumber.ToString();
if
(info.AllowScripts)
{
sb.Append(@"<A style='margin-left:5' href='javascript:' onclick=""
this.index=1-this.index;
var
index = this.index,
modes=new Array('none',''),
texts=new Array('Show details','Hide details');
this.nextSibling.style.display=modes[index];
this.innerText=texts[index];""
index=1>Hide details</A>"
);
}
sb.Append("<div style='margin-left:20;background-color:lightblue;'>")
.Append("<a name='").Append(ExtraInfoAnchor).Append("'></a>");
AppendExtraInfo(info);
sb.Append("</div>");
}
if
(bold) sb.Append("</B>");
else
if (smaller) sb.Append("</span>");
sb.Append("</DIV>");
}
FeedbackCollection children;
///
<summary>
///
Use this colleciton to add children. NB calling this function the first time will
///
create the collection
///
</summary>
public
FeedbackCollection Children
{
get
{
if (children == null)
{
children = new FeedbackCollection();
children.Parent = this;
}
return
children;
}
}
public bool HasChildren
{
get { return children != null && children.Count > 0; }
}

#region static
#region mail
public delegate void SendMailDelegate(string Subject, string Body, bool IsHTML);
static
SendMailDelegate sendmail = delegate(string Subject, string Body, bool IsHTML)
{
System.Net.Mail.MailMessage sm = new System.Net.Mail.MailMessage();
sm.IsBodyHtml = IsHTML;
sm.Body = Body;
sm.Subject = Subject;
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();
smtp.Send(sm);
};
public
static SendMailDelegate DefaultSendMailMethod
{
get { return sendmail; }
set

{
if (value == null)
throw new ArgumentNullException();
sendmail = value;
}
}
#endregion
#region Exceptions
public
delegate void ShowExceptionDelegate(Exception ex);
static
ShowExceptionDelegate showex =
delegate(Exception ex)
{
string m = ex.Message + "\r\n----------------\r\nStack:\r\n"
+ ex.StackTrace;
MessageBox.Show(m);
};
public static ShowExceptionDelegate DefaultShowExceptionMethod
{
get { return showex; }
set

{
if (value == null) throw new ArgumentNullException();
showex = value;
}
}
#endregion
public static implicit operator Feedback(string value)
{
return new Feedback(value);
}
#endregion
}
/// <summary>
///
General settings when the text of feedback is obtained.
///
<seealso cref="FeedbackCollection.GetTotalFeedbackText"/>
///
<see cref="Feedback.AppendInfo"/>
///
</summary>
public
class AppendInfoSettings
{
public AppendInfoSettings():this(new StringBuilder())
{
}
public
AppendInfoSettings(StringBuilder sb)
{
this.StringBuilder = sb;
}
public
readonly StringBuilder StringBuilder;
public
bool AllowScripts;
public
int MaxLines;
public AppendInfoSettings Append(string Value)
{
StringBuilder.Append(Value);
return
this;
}
public AppendInfoSettings Append<T>(T Value)
{
StringBuilder.Append(Value);
return
this;
}
}
/// <summary>
///
A collection of <see cref="Feedback"/> items. Besides being a collection
///
for different Feedback items, this class contains the methods to
///
visualize the feedback
///
</summary>
public
class FeedbackCollection : BindingList<Feedback>
{
public FeedbackCollection() { }
public
FeedbackCollection(int MaximumValues)
{
maxcount = MaximumValues;
}
/// <summary>
///
This value can be set to indicate that this set is a childset of
///
another Feedback item
///
</summary>
public
Feedback Parent;
#region Add/Update
protected override void InsertItem(int index, Feedback fb)
{
base.InsertItem(index, fb);
CheckMaxValues();            
}
protected override void OnListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
if
(Parent != null)
Parent.NotifyUpdate();
}

public Feedback Add(string Text)
{
Feedback fb = new Feedback(Text);
Add(fb);
return
fb;
}
public
Feedback Add(string Text, FeedBackLevel Level)
{
Feedback fb = new Feedback(Text);
fb.FeedbackLevel = Level;
Add(fb);
return
fb;
}
public
void Add(Feedback fb, int Progress)
{
fb.Progress = Progress;
Add(fb);
}
public ExceptionFeedback Add(Exception ex)
{
ExceptionFeedback ef = new ExceptionFeedback(ex);
Add(ef);
return
ef;
}
#endregion
#region Zero result
/// <summary>
///
By default this value is <c>false</c>, but the executing (batch) code
///
can set this value to indicate that all in all nothing has happened.
///
(eg, code to import file did not find a file)
///
To include a reason, use <see cref="SetZeroResult"/>
///
</summary>
public
bool ZeroResult
{
get { return lastzeroresult != null; }
set

{
if (ZeroResult != value)
{
if (value)
SetZeroResult("No reason specified");
else
LastZeroResult = null;
}
}
}
ZeroResultFeedback lastzeroresult;
internal ZeroResultFeedback LastZeroResult
{
get
{
return lastzeroresult;
}
set

{
if (lastzeroresult == value) return;
/*
if (Parent != null)
{
if (value != null)
Parent.LastZeroResult = value;
else if (Parent.lastzeroresult == lastzeroresult)
Parent.LastZeroResult = null;
}*/

lastzeroresult = value;
}
}
public class ZeroResultFeedback : Feedback
{
public readonly string Reason;
public
ZeroResultFeedback(string reason)
: base("Batch has zero result: [" + reason + "]")
{
this.FeedbackLevel = FeedBackLevel.Important;
this
.Reason = reason;
}
}

/// <summary>
///
The reason that was indicated that the code where this feedback
///
was used with did not actually do anything.
///
This value can be set when using <see cref="SetZeroResult"/>
///
</summary>
public
string ZeroResultReason
{
get
{
if (ZeroResult) return lastzeroresult.Reason;
return
null;
}
}
public
void SetZeroResult(string reason)
{
ZeroResultFeedback fb = new ZeroResultFeedback(reason);
Add(fb);
LastZeroResult = fb;
}
#endregion

public Feedback Last
{
get
{
return this[Count - 1];
}
}
public DateTime StartTime
{
get
{
if (Count == 0) return DateTime.MinValue;
return
this[0].DateTime;
}
}
public DateTime EndTime
{
get
{
if (Count == 0) return DateTime.MinValue;
return
Last.LastUpdate;
}
}
public TimeSpan Duration
{
get { return EndTime.Subtract(StartTime); }
}
private int maxcount;
/// <summary>
///
The maximum amount of entries allowed
///
</summary>
public
int MaximumValues
{
get { return maxcount; }
set

{
maxcount = value;
CheckMaxValues();
}
}
void CheckMaxValues()
{
if (maxcount > 0)
{
while (Count > maxcount)
RemoveAt(0);
}
}
#region Save/Mail
/// <summary>
///
Description is used when saving or mailing the list, so
///
this should contain information about the contents
///
</summary>
public
string Description = "Feedback list";
public
string Save()
{
string file = Application.CommonAppDataPath + @"\Feedback\";
if
(!Directory.Exists(file)) Directory.CreateDirectory(file);
string
notallowed = " /\\", descr = Description;
if (descr != null)
for (int i = 0; i < notallowed.Length; i++)
{
descr = descr.Replace(notallowed[i], '_');
}
else
descr = "feedback";
file += descr + ".htm";
Save(file);
return
file;
}
public
void Save(string file)
{
lock (this)
{
StreamWriter sw = new StreamWriter(file, true, System.Text.Encoding.ASCII);
sw.Write(GetTotalFeedBackText());
sw.Close();
Add("List saved to " + file);
}
}

public void Mail()
{
Mail(
(ZeroResult
? "Zero result "
: null)
+ "Batch Feedback '" + Description + "'");
}

public void Mail(string Subject)
{
Feedback.DefaultSendMailMethod(Subject,GetTotalFeedBackText(false),true);
}
public void MailExceptions()
{
Feedback.DefaultSendMailMethod("Errors in " + Description,
AppendFeedbackText(
new
AppendInfoSettings(),EnumerateAll<ExceptionFeedback>(true)).ToString(),
true

);
}
public string GetTotalFeedBackText()
{
return GetTotalFeedBackText(true);
}
public
string GetTotalFeedBackText(bool AllowScripts)
{
return AppendFeedbackText(new AppendInfoSettings { AllowScripts=AllowScripts}).ToString();
}        

public bool HasExceptions()
{
foreach (Feedback fb in this)
{
if (fb.FeedbackLevel== FeedBackLevel.Exception) return true;
if
(fb.HasChildren && fb.Children.HasExceptions())
return true;
}
return
false;
}
public IEnumerable<Feedback> EnumerateAll<FeedbackType>()
where FeedbackType : Feedback
{
return EnumerateAll<FeedbackType>(true);
}
public
IEnumerable<Feedback> EnumerateAll<FeedbackType>(bool IncludeChildren)
where FeedbackType : Feedback
{
foreach (Feedback fb in this)
{
if (fb is FeedbackType) yield return fb;
if
(IncludeChildren && fb.HasChildren)
{
foreach (FeedbackType f in fb.Children.EnumerateAll<FeedbackType>(true))
{
yield return f;
}
}
}
}
public StringBuilder AppendFeedbackText(StringBuilder sb, bool AllowScripts)
{
return AppendFeedbackText(new AppendInfoSettings(sb) { AllowScripts = AllowScripts });
}
public
StringBuilder AppendFeedbackText(AppendInfoSettings info)
{
return AppendFeedbackText(info, this);
}
public
StringBuilder AppendFeedbackText(AppendInfoSettings info, IEnumerable<Feedback> list)
{
var sb = info.StringBuilder;
sb.Append("<DIV style=\"border='1px black solid'\">");
if
(Parent == null)
sb.Append("\tFeedback ")
.Append(Description)
.Append(". Info created on ").Append(DateTime.Now);
if (ZeroResult)
sb.Append("<div style=\"border:'1 solid black';background-color:cyan\"><b>Finished with zero result: </b>")
.Append(ZeroResultReason)
.Append("</div>");
sb.Append("<HR>");
int
i = 0;
foreach
(Feedback fb in list)
{
fb.AppendInfo(info, i++);
if
(info.MaxLines > 0 && i == info.MaxLines) break;
}
sb.Append("</DIV>");
return
sb;
}
public void ShowHTML()
{
if (System.Threading.Thread.CurrentThread.GetApartmentState() == System.Threading.ApartmentState.STA)
{
Form f = new Form();
WebBrowser
wb = new WebBrowser();
wb.Dock = DockStyle.Fill;
f.Controls.Add(wb);
wb.DocumentText = GetTotalFeedBackText();
f.WindowState = FormWindowState.Maximized;
f.Show();
}
else
//Unfortunately, using the webbrowser requires an STA
//if this is not the case of the current thread, save and show with explorer

System.Diagnostics.Process.Start(Save());
}
#endregion

/// <summary>
///
Creates a <see cref="FeedbackForm"/> with a <see cref="FeedbackControl"/> and shows it or
///
shows an existing form if it was already created
///
</summary>
public
FeedbackForm Show()
{
return Show((IWin32Window)null);
}
public
FeedbackForm Show(IWin32Window Owner)
{
if (Owner is Control)
return Show(Owner as Control);
if (frm == null)
{
CreateFeedbackForm();
frm.Show(Owner);
}
else
frm.Activate();
return frm;
}
delegate FeedbackForm show(Control owner);
public FeedbackForm Show(Control Owner)
{
if (Owner == null) return Show();
if
(Owner.InvokeRequired)
return (FeedbackForm)
Owner.Invoke(new show(Show), Owner);
if (frm == null)
{
CreateFeedbackForm(Owner);
Form f = Owner.FindForm();
if
(f != null)
{
if (f.IsMdiContainer)
frm.MdiParent = f;
else if (f.IsMdiChild)
frm.MdiParent = f.MdiParent;
}
if (frm.MdiParent == null)
frm.Show(Owner);
else
frm.Show();
}
else
frm.Activate();
return frm;
}
FeedbackForm
frm;

public void ShowDialog()
{
ShowDialog(null);
}
public void ShowDialog(IWin32Window Owner)
{
CreateFeedbackForm().ShowDialog(Owner);
}
/// <summary>
///
creates a feedbackform based on this collection
///
</summary>
///
<returns></returns>
public
FeedbackForm CreateFeedbackForm()
{
createForm();
return
frm;
}
void createForm()
{
if (frm == null)
frm = new FeedbackForm(this);
}
///
<summary>
///
This overload of <see cref="CreateFeedbackForm()"/> makes sure
///
the <see cref="FeedbackForm"/> is created on the same thread
///
as the <c>ThreadControl</c>
///
</summary>
public
FeedbackForm CreateFeedbackForm(Control ThreadControl)
{
if (ThreadControl == null || !ThreadControl.InvokeRequired)
return CreateFeedbackForm();
CloseFeedbackForm();
ThreadControl.Invoke(new System.Threading.ThreadStart(createForm));
return
frm;
}
/// <summary>
///
If a <see cref="FeedbackForm"/> is shown, this method will close it
///
</summary>
public
void CloseFeedbackForm()
{
if (frm != null)
{
try
{
if (frm.InvokeRequired)
frm.Invoke(new System.Threading.ThreadStart(frm.Dispose));
else
frm.Dispose();
}
catch
{ }
frm = null;
}
}
/*
internal void FormClosed()
{
frm = null;
}
* */
}
///
<summary>
///
Basically just a form with a <see cref="FeedbackControl"/>
///
</summary>
public
class FeedbackForm : Form
{
FeedbackControl fc = new FeedbackControl();        

public
FeedbackForm()
{
Text = DefaultText;      
KeyPreview = true;            
fc.CollectionChanged += new EventHandler(fc_CollectionChanged);
fc.Dock = DockStyle.Fill;
Controls.Add(fc);
}
public FeedbackForm(FeedbackCollection collection)
: this()
{
this.Collection = collection;
}
public Feedback Add(Feedback fb)
{
return fc.Add(fb);
}
public FeedbackCollection Collection
{
get { return fc.Collection; }
set
{ fc.Collection = value; }
}

void fc_CollectionChanged(object sender, EventArgs e)
{
SetText();
}
const string DefaultText = "Feedback information";
void
SetText()
{
string text = DefaultText;
if
(Collection!=null && Collection.Description!=null)
text = "[" + Collection.Description + "] " + text;
Text = text;
}
public static Size DefaultFormSize = new Size(300, 300);
protected
override Size DefaultSize
{
get
{
return DefaultFormSize;
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape) Close();
base
.OnKeyDown(e);
}
protected override void OnClosed(EventArgs e)
{
Collection.CloseFeedbackForm();
}
}
/// <summary>
///
A control containing a <see cref="FeedbackListBox"/> and
///
some extra linklabels for extra gui options
///
</summary>
public
class FeedbackControl : Control
{
FeedbackListBox lb = new FeedbackListBox();
BottomPanel
pnlBottom;
public
FeedbackControl()
{
lb.Dock = DockStyle.Fill;
lb.CollectionChanged += new EventHandler(lb_DataSourceChanged);
Controls.Add(lb);
pnlBottom = new BottomPanel();
Controls.Add(pnlBottom);
}
void lb_DataSourceChanged(object sender, EventArgs e)
{
pnlBottom.Visible = Collection != null;
}
/// <summary>
///
By Asigning a collection, the listbox will update
///
itself when messages are received.
///
NB: each collection can have only one FeedbackControl asigned to it.
///
</summary>
[DefaultValue(null)]
public
FeedbackCollection Collection
{
get { return lb.Collection; }
set

{
lb.Collection = value;
}
}
public event EventHandler CollectionChanged
{
add { lb.CollectionChanged += value; }
remove
{ lb.CollectionChanged -= value; }
}
class BottomPanel : Panel
{
LinkLabel
llMail = new LinkLabel(),
llSave = new LinkLabel(),
llShow = new LinkLabel();
public BottomPanel()
{
Dock = DockStyle.Bottom;
Padding = new Padding(1);
Height = 20;
AddLabel(llMail);
AddLabel(llSave);
AddLabel(llShow);
llMail.Click += new EventHandler(llMail_Click);
llMail.Text = "Mail list";
llSave.Click += new EventHandler(llSave_Click);
llSave.Text = "Save list";
llShow.Click += new EventHandler(llShow_Click);
llShow.Text = "Show overview";
}
void llShow_Click(object sender, EventArgs e)
{
if (InvokeRequired)
Invoke(new EventHandler(llShow_Click), sender, e);
else
coll.ShowHTML();
}
new FeedbackControl Parent
{
get { return base.Parent as FeedbackControl; }
}
FeedbackCollection
coll
{
get { return Parent.Collection; }
}
void
llSave_Click(object sender, EventArgs e)
{
if (InvokeRequired)
Invoke(new EventHandler(llSave_Click), sender, e);
else
try
{
SaveFileDialog sf = new SaveFileDialog();
sf.Filter = "HTML file|*.htm;*.html";
if (sf.ShowDialog(this) == DialogResult.OK)
{
coll.Save(sf.FileName);
}
}
catch
(Exception ex)
{
MessageBox.Show("Error saving list:\r\n" + ex.Message);
}
}
void llMail_Click(object sender, EventArgs e)
{
if (InvokeRequired)
Invoke(new EventHandler(llMail_Click), sender, e);
else
try
{
coll.Mail();
}
catch
(Exception ex)
{
MessageBox.Show("Error mailing list:\r\n" + ex.Message);
}
}
void AddLabel(LinkLabel ll)
{
ll.Dock = DockStyle.Left;
Controls.Add(ll);
}
}
public Feedback Add(Feedback fb)
{
return lb.Add(fb);
}
public int Count
{
get { return lb.Count; }
}
public Feedback this[int Index]
{
get { return lb[Index]; }
}
public class FeedbackAlivePanel : Panel
{
PulseRectangle pr = new PulseRectangle();
public
FeedbackAlivePanel()
{
SetStyle(ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint, true);
}
protected override void OnResize(EventArgs eventargs)
{
pr.Bounds = Bounds;
Increase();
}
public void Increase()
{
Invalidate();
}

protected override void OnPaint(PaintEventArgs e)
{
pr.Paint(e.Graphics);
}
}
}
/// <summary>
///
A listbox specifically for feedback items. The items are graphically shown to clearly
///
outline their type of feedback
///
</summary>
public
class FeedbackListBox : ListBox
{
public FeedbackListBox()
{
DrawMode = DrawMode.OwnerDrawFixed;
SetStyle(ControlStyles.AllPaintingInWmPaint
| ControlStyles.OptimizedDoubleBuffer, true);
items = new ItemCollection(this);
}
public Feedback this[int index]
{
get
{
if (coll == null)
throw new Exception("No collection has been started yet");
return coll[index];
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility .Hidden)]
public
new ItemCollection Items
{
get { return items; }
}
ItemCollection items;
public
class ItemCollection
{
public readonly FeedbackListBox Owner;
public
ItemCollection(FeedbackListBox Owner)
{
this.Owner = Owner;
}
public Feedback this[int Index]
{
get
{
return Owner[Index];
}
}
public int Count { get { return Owner.Count; } }
public void Add(Feedback fb)
{
Owner.Add(fb);
}
public int IndexOf(Feedback fb)
{
return Owner.IndexOf(fb);
}
}
public Feedback Add(Feedback fb)
{
if (coll == null) Collection = new FeedbackCollection();
coll.Add(fb);
return
fb;
}

void Collection_ListChanged(object sender, ListChangedEventArgs e)
{
switch (e.ListChangedType)
{
case ListChangedType.ItemAdded:
case
ListChangedType.ItemChanged:
case
ListChangedType.ItemDeleted:
//this inbetween class is used because in threading environments
//the collection can be updated before the invoke is finished

update(new updateinfo
{
Index=e.NewIndex,
Item=((FeedbackCollection)sender)[e.NewIndex],
Type= e.ListChangedType
});
break
;
}
}
class updateinfo
{            
public int Index;
public
Feedback Item;
public
ListChangedType Type;
}
Queue<updateinfo> updates = new Queue<updateinfo>();
volatile
bool updating;
void
update(updateinfo info)
{
lock (updates)
updates.Enqueue(info);
try
{
if (!updating && !IsDisposed)
BeginInvoke((Void)delegate()
{
update();
});
}
catch
(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void update()
{
if (updating) return;
updating = true;
updateinfo
info ;
while
(updates.Count>0)
{
lock(updates)
info = updates.Dequeue();
switch (info.Type)
{
case ListChangedType.ItemAdded:
Init(info.Item, info.Index);
break
;
case ListChangedType.ItemChanged:
Update(info.Item, info.Index);
break
;
case ListChangedType.ItemDeleted:
Remove(info);
break
;
}
}
Application
.DoEvents();
updating = false;
}

void Init(Feedback fb, int index)
{
fb.Updated += new EventHandler(Feedback_Updated);                        
base
.Items.Insert(index, fb);
SetIndex(index);
}
void
Remove(updateinfo inf)
{
inf.Item.Updated -= new EventHandler(Feedback_Updated);
base
.Items.RemoveAt(inf.Index);
}
void Feedback_Updated(object sender, EventArgs e)
{
var u = new updateinfo();
u.Item= (Feedback)sender;
u.Index=IndexOf(u.Item);
u.Type =u.Index==-1 ? ListChangedType.ItemDeleted : ListChangedType.ItemChanged;
update(u);
}
delegate void Void();
void UpdateSummary()
{
}
void Update(Feedback fb, int index)
{
if (fb.IsAliveIndicator)
UpdatePulse(fb);
//System.Diagnostics.Debug.Assert(index >= 0);
if
(index >= 0)
{
Rectangle r = GetRectangle(index);
Invalidate(r);
SetIndex(index);
Application
.DoEvents();
}
else
fb.Updated -= new EventHandler(Feedback_Updated);
}

Rectangle GetRectangle(int index)
{
Rectangle r = ClientRectangle;
r.Height = this.ItemHeight;
r.Y = r.Height*index;
return
r;
}
void
SetIndex(int index)
{
SelectedIndex = index;
}
public int IndexOf(Feedback fb)
{
if (coll == null) return -1;
return
coll.IndexOf(fb);
}
public
int Count
{
get
{
if (coll == null) return 0;
return
coll.Count;                
}
}
protected override void OnDrawItem(DrawItemEventArgs e)
{

object
item = base.Items[e.Index];
Feedback
fb = (Feedback)item ;
FeedbackItemInfo
inf = new FeedbackItemInfo(fb,e,coll.StartTime,this);
fb.GetPainter(inf).Paint();
}

public event EventHandler CollectionChanged;
FeedbackCollection coll;
[DefaultValue(null)]
public
FeedbackCollection Collection
{
get
{
return coll;
}
set

{
if (coll == value) return;
BeginUpdate();
base
.Items.Clear();
if
(coll!=null)
coll.ListChanged += new ListChangedEventHandler(Collection_ListChanged);
//base.DataSource =  DataSource not used because of threading problems
coll = value;                
if
(coll != null)
{                    
coll.ListChanged += new ListChangedEventHandler(Collection_ListChanged);
for
(int i = 0; i < coll.Count; i++)
{
Init(coll[i], i);
}                    
}
EndUpdate();
if
(CollectionChanged != null)
CollectionChanged(this, EventArgs.Empty);
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility .Hidden)]
public
new FeedbackCollection DataSource
{
get { return coll; }
}
Dictionary<Feedback, PulseRectangle> pulses;
void
UpdatePulse(Feedback fb)
{
GetPulse(fb).Update();
}
public PulseRectangle GetPulse(Feedback fb)
{
if (pulses == null)
pulses = new Dictionary<Feedback, PulseRectangle>();
PulseRectangle pr;
if
(!pulses.TryGetValue(fb, out pr))
{
pr = new PulseRectangle();
pr.Count = 20;
pulses.Add(fb, pr);
}
return
pr;
}
protected override void OnClick(EventArgs e)
{
if (mouseonbutton != null)
{
mouseonbutton.ShowExtraInfo(this);
}
base
.OnClick(e);
}
Feedback mouseonbutton;

protected
override void OnMouseMove(MouseEventArgs e)
{

Feedback
onbutton = null;

if
(Count > 0 && FeedbackItemPainter.ExtraInfoButtonArea.Contains(e.X,e.Y))
{
int index = IndexFromPoint(e.Location);
if
(index != -1 && index < Count)
onbutton = this[index];
}
if (mouseonbutton != onbutton)
{
if (onbutton != null && onbutton.CanShowExtraInfo)
mouseonbutton = onbutton;
else
mouseonbutton = null;
Cursor = mouseonbutton != null ? Cursors.Hand : null;
}
}
}
public class FeedbackItemInfo
{        
public readonly DrawItemEventArgs DrawItemEventArgs;
public
readonly bool IsSelected;
public
readonly Feedback Item;
public
readonly int Index;
public
readonly Rectangle Bounds;
public
readonly DateTime CollectionStartTime;
public
readonly FeedbackListBox Owner;
public FeedbackItemInfo(Feedback Item, DrawItemEventArgs e, DateTime CollectionStartTime, FeedbackListBox Owner)
{
this.Item = Item;
this
.DrawItemEventArgs = e;            
this
.IsSelected = (e.State & DrawItemState.Selected) > 0;
this
.Index = e.Index;            
this
.Owner = Owner;
this
.Bounds = e.Bounds;
this
.CollectionStartTime = CollectionStartTime;
}
}
public class FeedbackItemPainter
{
Rectangle r;
public
FontFamily FontFamily;
public
readonly Rectangle MessageArea;
public
readonly FeedbackItemInfo Info;
public
readonly Graphics Graphics;
public
FeedbackItemPainter(FeedbackItemInfo info)
{
this.Info = info;
this
.r = info.Bounds;
this
.FontFamily = info.DrawItemEventArgs.Font.FontFamily;
MessageArea = new Rectangle(MessageOffset, r.Y, r.Width - MessageOffset, r.Height);
this
.Graphics = info.DrawItemEventArgs.Graphics;
}
public const int
ExtraInfoOffset = 75,
ExtraInfoWidth = 10,
ProgressWidth = 40,
MessageOffset = 90,
TimeOffset = 32;
public static readonly Rectangle ExtraInfoButtonArea = new Rectangle(ExtraInfoOffset, 0, ExtraInfoWidth, int.MaxValue);

public void DrawDateTime()
{
DrawDateTime(Info.Index > 0,Brushes.Black);
}
public
virtual void DrawDateTime(bool Relative,Brush b)
{
//clear background
ClearDateTimeBackGround(Brushes.White);
//draw text

if
(Relative)
DrawRelativeTime(Info.Item.LastUpdate.Subtract(Info.CollectionStartTime),b);
else
DrawAbsoluteTime(b);
}
void DrawAbsoluteTime(Brush b)
{
Graphics.DrawString(
Info.Item.DateTime.ToShortDateString() + " " + Info.Item.DateTime.ToString("hh:mm.ss"),
GetFont(6.5f), b, r.Left, r.Top + 1);
}
void DrawRelativeTime(TimeSpan diff,Brush b)
{
Graphics.DrawString(
string.Format("+{0:00}:{1:00}.{2:00}.{3:000}",
diff.Hours, diff.Minutes, diff.Seconds, diff.Milliseconds),
GetFont(7.2f)
, b, r.Left, r.Top);
}
public Font GetFont(float size)
{
return new Font(FontFamily, size);
}
public virtual void ClearDateTimeBackGround(Brush b)
{
Graphics.FillRectangle(b, DateTimeArea);
}
public Rectangle DateTimeArea
{
get
{
return new Rectangle(r.Left, r.Top, r.Left + ExtraInfoOffset, r.Height);
}
}
public virtual Color GetBackgroundColor()
{
if(Info.IsSelected)
return Color.Blue;
return Color.White;
}
public void DrawSelectionRectangle()
{
DrawSelectionRectangle(GetBackgroundColor());
}
public
virtual void DrawSelectionRectangle(Color c)
{
Graphics.DrawRectangle(new Pen(c), r);
ClearMessageBackGround(c);
}

public
void ClearMessageBackGround(Color c)
{
Graphics.FillRectangle(new SolidBrush(c), MessageArea);
}
public void DrawExtraInfoButton()
{
Rectangle rectEI = ExtraInfoButtonArea;
rectEI.Y = r.Y;
rectEI.Height = r.Height;
DrawExtraInfoButton(rectEI);
}
public virtual void DrawExtraInfoButton(Rectangle r)
{
ControlPaint.DrawButton(Graphics, r, ButtonState.Normal);
r.X += 2;
Graphics.DrawString("i", GetFont(7), Brushes.Blue, r);
}
public void DrawMessage()
{
DrawMessage(offset);
}
public void DrawMessage(int Offset)
{
Color c = Info.IsSelected ? Color.WhiteSmoke : Color.Black;            
Rectangle
r = MessageArea;
r.X += Offset;
r.Width -= Offset;
DrawMessage(r, new SolidBrush(c));
}
public
virtual void DrawMessage(Rectangle r,Brush b)
{            
Font f = GetFont(7);
if
(Info.Item.Level > 0)
f = new Font(f, FontStyle.Bold);
Graphics.DrawString(Info.Item.Message, f, b, r);
}
int offset;

public virtual void PaintProgress()
{
RectangleF rectProg = GetProgressRectangle();
rectProg.X++;
rectProg.Inflate(0, -1);
Graphics.FillRectangle(Brushes.White, rectProg);
Pen
p = Pens.Black;
Graphics.DrawRectangle(p, Rectangle.Round(rectProg));
float
penwidth = .5f;
rectProg.Inflate(-penwidth, -penwidth);
rectProg.Width *= Info.Item.ProgressPercentage;
Graphics.FillRectangle(Brushes.Green, rectProg);
Font
f = GetFont(7);
Graphics.DrawString(Info.Item.GetProgressString(), f,
Brushes.Navy, rectProg.Left + 1, rectProg.Top - 1);
}
public virtual void PaintAlive()
{
Rectangle r = GetProgressRectangle();
r.Inflate(0, -1);
PulseRectangle
pr = Info.Owner.GetPulse(Info.Item);
pr.Bounds = r;
pr.Paint(Graphics);
}
protected Rectangle GetProgressRectangle()
{
Rectangle r = MessageArea;
r.X += offset;
offset += ProgressWidth + 2;
r.Width = ProgressWidth;
return
r;
}
protected void IncreaseOffset(int by)
{
offset += by;
}
public virtual void Paint()
{
DrawDateTime();
if (Info.Item.CanShowExtraInfo)
{
DrawExtraInfoButton();
}
DrawSelectionRectangle();
//draw progress
if
(Info.Item.Progress > 0)
{
PaintProgress();
}
//draw alive
if
(Info.Item.IsAliveIndicator)
{
PaintAlive();
}
//draw message
DrawMessage();
}
}
public class PulseRectangle
{
private Rectangle bounds;
public Rectangle Bounds
{
get { return bounds; }
set

{
if (bounds == value) return;
Rectangle
r = bounds;
bounds = value;
if
(r.Width != value.Width || ps == null)
Update();
else
FillDrawPoints();
}
}
void transform(int x, int y)
{
if (x == 0 && y == 0) return;
for
(int i = 0; i < ps.Length; i++)
{
ps[i].X -= x;
ps[i].Y -= y;
}
}
/// <summary>
///
The number of points to draw.
///
Call Update manually after setting this value
///
</summary>
public
int Count = 200;
private Point[] ps, drawpoints;
public
void Update()
{
Random r = new Random();
double
step = (double)bounds.Width / Count;
ps = new Point[Count];
drawpoints = null;
for
(int i = 0; i < Count; i++)
ps[i] = new Point((int)(step * i), r.Next(100));
FillDrawPoints();
}
///
<summary>
///
The pen with which the pulses are drawn
///
</summary>
public
Pen Pen = Pens.LightGreen;
public Brush BackGroundBrush = Brushes.Black;
void FillDrawPoints()
{
int left = bounds.Left, h = bounds.Height, top = bounds.Top;
drawpoints = new Point[ps.Length];
for
(int i = 0; i < ps.Length; i++)
{
drawpoints[i].X = ps[i].X + left;
drawpoints[i].Y = top + (int)(h * ps[i].Y / 100);
}
}
public
void Paint(Graphics g)
{
if (ps == null) Update();
g.FillRectangle(BackGroundBrush, bounds);
g.DrawLines(Pen, drawpoints);
}
}
/// <summary>
///
The importance of the feedback
///
</summary>
public
enum FeedBackLevel
{
Custom = 100,
Trivial = -100,
Normal = 0,
SuperNormal = 200,
Exception = 500,
Important = 1000
}
public interface IFeedBackSupporter
{
FeedbackCollection Feedback { get; }
}
#region Specific feedback
public
class FileReferenceFeedback : Feedback
{
public readonly string File;
public
FileReferenceFeedback(string MessagePrefix, string File)
: base(
MessagePrefix
+ System.IO.Path.GetFileName(File)
+ " in "
+ System.IO.Path.GetDirectoryName(File))
{
this.File = File;
}
public
FileReferenceFeedback(string File)
: this("File: ", File)
{
}
public override bool CanShowExtraInfo
{
get
{
return true;
}
}
public override void ShowExtraInfo(IWin32Window Owner)
{
System.Diagnostics.Process.Start("Explorer", "/select," + File);
}
}
public class ExceptionFeedback : Feedback
{
public readonly Exception Exception;
public
ExceptionFeedback(Exception ex)
{
Exception = ex;
FeedbackLevel = FeedBackLevel.Exception;
System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(ex);
Message = "Error '" + ex.Message + "' occured on " + stack.GetFrame(0);
}
public override bool CanShowExtraInfo
{
get
{
return true;
}
}           
public override void ShowExtraInfo(IWin32Window Owner)
{
DefaultShowExceptionMethod(Exception);            
}
class ErrorPainter : FeedbackItemPainter
{
public ErrorPainter(FeedbackItemInfo inf)
: base(inf)
{
}
public override void DrawMessage(Rectangle r, Brush b)
{
Rectangle ri = r;
ri.Width = ri.Height;
Graphics.DrawIcon(SystemIcons.Error, ri);
r.X = ri.Right;
base
.DrawMessage(r,Info.IsSelected ? Brushes.Orange : Brushes.Red);
}
}
public override FeedbackItemPainter GetPainter(FeedbackItemInfo info)
{
return new ErrorPainter(info);
}
protected override void AppendExtraInfo(AppendInfoSettings info)
{
new ExceptionInfo(Exception).AppendMessage(info.StringBuilder);
}
}
#endregion
}
. . .

Also needed: (this is used to show complete exception information in HTML, you could also strip the exception adding functionality, but I use this class a lot to quickly debug and get better stacktraces than the default .net block of code.
Code CopyHideScrollFull
using System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Reflection;
using
System.Diagnostics;
using
System.Data.SqlClient;
using
System.IO;

namespace
Subro.Exceptions
{
public class ExceptionInfo : IExtraInfo
{
/// <summary>
///
The original Exception where this info object is based on
///

public
readonly Exception Exception;
public
ExceptionInfo(Exception ex)
{
this.Exception = ex;
//get stack trace information
StackTrace
st = new StackTrace(Exception, true);
frames = new ErrorFrame[st.FrameCount];
for
(int i = 0; i < frames.Length; i++)
{
frames[i] = new ErrorFrame(st.GetFrame(i));
if
(usercodeindex == -1 && frames[i].IsUserCode)
usercodeindex = i;
}
//determine total exception count
while
(ex != null)
{
exceptioncount++;
ex = ex.InnerException;
}
}
#region General properties
///
<summary>
///
The date/time of the exception. This is not the time the exception
///
was thrown, but rather when the ExceptionInfo object was created.
///
For most debugging purposes that difference in time does not matter, but
///
when the exact time is required, do not rely on this value!
///

public
readonly DateTime DateTime = DateTime.Now;
ErrorFrame[] frames;
///
<summary>
///
Gets the different StackFrames that led to this
///

public
ErrorFrame[] Frames
{
get { return frames; }
}
public int FrameCount
{
get { return frames.Length; }
}
int usercodeindex = -1;
///
<summary>
///
Returns the index of the first frame that occured in user code (and thus
///
the first frame that is actually debuggable ;-) )
///

public
int UserFrameIndex
{
get { return usercodeindex; }
}

/// <summary>
///
Information about the last step before this error occured. The returned
///
object contains information about that step, such as the method that was running.
///
When running in debug, the pdb files also provide info on the original code filename and line number
///

public
ErrorFrame LastFrame
{
get
{
if (frames.Length > 0) return frames[0];
return
null;
}
}
/// <summary>
///
Where <see cref="LastFrame"/> returns the last step in general, this property
///
returns the last step in non-system code
///
<seealso cref="UserFrameIndex"/>
///

public
ErrorFrame LastUserFrame
{
get
{
if (usercodeindex == -1) return null;
return
frames[usercodeindex];
}
}
/// <summary>
///
The exception type
///

public
string Type
{
get { return Exception.GetType().Name; }
}
#endregion
#region Mail NB: framework specific
///
This mail section was created in .net 2.0, you might
///
want to remove this part or alter it if using earlier versions
/// <summary>
///
Sends an email to the specified address
///

///
<param name="To">
public
void Send(string To)
{
System.Net.Mail.MailMessage m = GetExceptionMail();
m.To.Add(To);
System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient("mail");
client.Send(m);
}
/// <summary>
///
Send the exception info (in html format) to the addressee.
///
NB, some presumtions were made when trying to create a quick and
///
dirty send method. Safest way it to use <see cref="FillMailMessage"/> or
///
<see cref="GetExceptionMail"/> and do the sending manually
///

///
<param name="ex">
///
<param name="To">
public
static void Send(Exception ex, string To)
{
GetInfo(ex).Send(To);
}
/// <summary>
///
Gets a mailmessage instance containing the Exception info.
///
The from address is tried to be set to a custom address. If this
///
fails (or if you want to set a custom one), you'll have to set the From
///
address manually.
///

///
<returns>
public
static System.Net.Mail.MailMessage GetExceptionMail(Exception ex)
{
return GetInfo(ex).GetExceptionMail();
}
System.Net.Mail.MailMessage GetExceptionMail()
{
System.Net.Mail.MailMessage m = new System.Net.Mail.MailMessage();
FillMailMessage(m);
try

{
m.From = new System.Net.Mail.MailAddress("Exceptions@" + Environment.UserDomainName);
}
catch
{ }
return
m;
}
/// <summary>
///
Sets the body of the message to hold the exeption info
///

///
<param name="m">
public
void FillMailMessage(System.Net.Mail.MailMessage m)
{
m.IsBodyHtml = true;
m.Body = GetTotalMessage();
}
#endregion
#region ExtraInfo NB
/// <summary>
///
This class can hold extra info for an <see cref="ExceptionInfo"/>
///
Its main purpose is to output this extra info to the html text
///

public
abstract class ExtraInfo : IExtraInfo
{
/// <summary>
///
With this method, the extra info writes itself (in html format) to
///
the stringbuilder
///

///
<param name="sb">
public
abstract void AppendHTML(StringBuilder sb, ExceptionInfo ei);
public static implicit operator ExtraInfo(string text)
{
return new TextInfo(text);
}
}
public class ValuesInfo : ExtraInfo
{
List<ValueEntry> values = new List<ValueEntry>();
class
ValueEntry
{
public string Name, Text;
}
public
string Header;
public void AddText(string text)
{
AddValue(null, text);
}
public
void AddValue(string Name, string Value)
{
ValueEntry v = new ValueEntry();
v.Name = Name;
v.Text = Value;
values.Add(v);
}
public override void AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
ei.openBlock(1, 1);
if
(Header != null)
sb.Append("<b><i>").Append(Header).Append(":");
if (values.Count > 0)
{
ei.openBlock(1, 1);
foreach
(ValueEntry ve in values)
{
if (ve.Name == null)
sb.Append(ve.Text);
else
ei.appendInfo(ve.Name, ve.Text);
}
ei.closeBlock();
}
ei.closeBlock();
}
}
/// <summary>
///
Adds plain text as extrainfo
///

public
class TextInfo : ExtraInfo
{
public string Text;
public
TextInfo(string text) { Text = text; }
public
override void AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
sb.Append(Text);
}
}
#region SQL extra info
/// <summary>
///
Used to display the history of sql strings.
///

public
class SQlStringInfo : ExtraInfo
{
List<string> list = new List<string>();
public
void Add(string sql)
{
list.Add(sql);
}
public void AddRange(IEnumerable<string> values)
{
list.AddRange(values);
}
public override void AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
ei.openBlock(1);
sb.Append("<B>Recent sql strings:");
ei.openBlock("border:'gray 1 solid';margin-left:15");
int
i = 0;
foreach
(var sql in list)
{
sb.Append("<SPAN><B>--")
.Append(i++ + 1)
.Append("--<span style='margin-left:10'>")
.Append(sql)
.Append("<BR>");
}
ei.closeBlock();
ei.closeBlock();
}
}
/// <summary>
///
Add one or more sql strings to the list to be displayed in the recent
///
sql list.
///
This is usefull when you keep a list of recent executed sql strings somewhere
///
and want to include them in the exception output
///

///
<param name="SQL">
public
void AddSQL(IEnumerable<string> SQL)
{
if (sqls == null)
{
sqls = new SQlStringInfo();
AddExtraInfo(sqls);
}
sqls.AddRange(SQL);
}
SQlStringInfo sqls;
#endregion
/// <summary>
///
Add <see cref="ExtraInfo"/>. You can also use a string as parameter
///

///
<param name="ei">
public
void AddExtraInfo(IExtraInfo ei)
{
if (extrainfo == null) extrainfo = new List<IExtraInfo>();
extrainfo.Add(ei);
}
List<IExtraInfo> extrainfo;
/// <summary>
///
returns the amount of <see cref="ExtraInfo"/> objects added
///

public
int ExtraInfoCount
{
get
{
if (extrainfo == null) return 0;
return
extrainfo.Count;
}
}
#region HTML
const
string ExtraInfoAnchor = "ExtraInfo";
protected
virtual void appendExtraInfo()
{
if (extrainfo != null)
{
appendBR();
appendHR();
openBlock(2);
sb.Append("<B><A name='").Append(ExtraInfoAnchor)
.Append("'>Extra Info<BR><HR>");
openBlock("margin-left:10;font-size:smaller");
foreach
(IExtraInfo ei in extrainfo)
{
ei.AppendHTML(sb, this);
}
closeBlock();
closeBlock();
}
}
#endregion
#endregion
#region Static
///
<summary>
///
Rather than creating a new instance manually, use
///
this method to choose the proper exception object
///

///
<param name="ex">
///
<returns>
public
static ExceptionInfo GetInfo(Exception ex)
{
if (ex == null) return null;
if
(ex is SqlException) return new SqlExceptionInfo(ex as SqlException);
if
(ex is ReflectionTypeLoadException) return new ReflectionTypeLoadExceptionInfo(ex as ReflectionTypeLoadException);
return
new ExceptionInfo(ex);
}
public
static implicit operator ExceptionInfo(Exception ex)
{
return GetInfo(ex);
}
#endregion
#region Inner Exceptions
int
exceptioncount;
///
<summary>
///
returns the total number of exceptions (Main Exception + all inner exceptions)
///

public
int ExceptionCount
{
get { return exceptioncount; }
}
#endregion
#region html information
///
BEWARE: this code is manufactured to quickly create the html information
///
and is not nicely constructed for reusability.
///
A bit more friendly code for html is used in the AutoFormatter (http://blogs.vbcity.com/hotdog/archive/2005/12/30/5759.aspx) ,
///
but did not use that here to keep the code portable for fresh applications
/// <summary>
///
Gets information about this exception and all inner exceptions in HTML format
///

///
<returns>
public
string GetTotalMessage()
{
return AppendMessage(new StringBuilder()).ToString();
}
/// <summary>
///
This stringbuilder is only used in the html functions
///

protected
StringBuilder sb;
protected
void openBlock()
{
openBlock(null);
}
protected
void openBlock(int Border)
{
openBlock(Border, 0);
}
protected
void openBlock(int Border, int Indent)
{
openBlock("Border='" + Border + "px black solid'"
+ (Indent > 0 ? "margin-left=" + (Indent * 20) : null)
);
}
protected
void openBlock(string Style)
{
openclose(false, Style);
}
protected
void closeBlock()
{
openclose(true, null);
}
void
openclose(bool close, string Style)
{
sb.Append("<");
if
(close) sb.Append("/");
sb.Append("DIV");
if
(Style != null)
sb.Append(" Style=\"").Append(Style).Append("\"");
sb.Append(">");
}

public
StringBuilder AppendMessage(StringBuilder builder)
{
sb = builder;
openBlock(2);
appendTop();
appendInfo("Exception info created on", DateTime.ToString());
appendInfo("Application", AppDomain.CurrentDomain.FriendlyName);
openBlock("font-size:smaller;margin-left:20");
appendHeaderBottom();
closeBlock();
Exception
ex = Exception;
int
depth = 0;
while
(append(ex, depth++)) { ex = ex.InnerException; }
appendExtraInfo();
appendBottom();
closeBlock();
sb = null;
return
builder;
}
void appendMessage()
{
}
void IExtraInfo.AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
this.sb = sb;
openBlock(2, 1);
appendTitle("Exception Info");
try

{
AppendMessage(sb);
}
catch
(Exception ex)
{
appendTitle("Error obtaining exception info: " + ex.Message);
}
closeBlock();
}

/// <summary>
///
Gives inheriting classes the possibility to append html text at the top
///
of the info block, inside the main border
///

protected
virtual void appendTop()
{
}
///
<summary>
///
Gives inheriting classes the possibility to append html inside the header
///
of the info block.
///
The base functionality adds links to the main and inner exceptions (if there are any)
///

protected
virtual void appendHeaderBottom()
{
if (exceptioncount > 1)
{
for (int i = 0; i < exceptioncount; i++)
{
openHeaderLink(AnchorNamePrefix + i);
AppendExceptionTitle(i);
sb.Append("");
}
}
if
(extrainfo != null)
{
openHeaderLink(ExtraInfoAnchor);
sb.Append("Extra Info");
}
}
/// <summary>
///
Gives inheriting classes the possibility to append html inside the header
///
of the Exception info block.        
///

protected
virtual void appendExceptionHeader()
{
}
/// <summary>
///
Used to add links in the header
///

///
<param name="href">
///
<param name="name">
protected
virtual void openHeaderLink(string href)
{
sb.Append("<A style='margin-left:15' href='#").Append(href)
   .Append("'>");
}


/// <summary>
///
Gives inheriting classes the possibility to append html text at the
///
end of the exception info, but before the main block is closed
///

protected
virtual void appendBottom()
{
}
/// <summary>
///
The prefix of the name that is added per depth so that code can
///
point directly to one of the inner exceptions
///
The Name for the first exception is ExDepth0 , the second ExDepth1 and so forth.
///
Pointing to it in a href is subseqeuntly done with href = "#ExDepthX" where X is the inner exception index
///

public
const string AnchorNamePrefix = "ExDepth";
void AppendExceptionTitle(int depth)
{
if (depth == 0)
sb.Append("Main Exception");
else
sb.Append("Inner Exception [").Append(depth).Append("]");
}
bool append(Exception ex, int depth)
{
if (ex == null) return false;
appendHR();
//add anchor information

sb.Append("<A NAME=\"").Append(AnchorNamePrefix).Append(depth)
.Append("\" style='font-size:smaller'>");
AppendExceptionTitle(depth);
sb.Append(": <span style='background-color:#990000;color:white;font-weight:bolder;width:100%'>")
.Append(ex.Message)
.Append("");
openBlock(1, 1);
if
(depth > 0)
{
ExceptionInfo ei = GetInfo(ex);
ei.sb = sb;
ei.appendHeader();
try

{
ei.appendExtraInfo();
}
catch
(Exception ex2)
{
appendTitle("Error writing extra info: " + ex2.Message);
}
}
else
appendHeader();
closeBlock();
return
true;
}
const string
usercodecolor = "lightblue",
systemcodecolor = "beige";
void appendHeader()
{
openBlock("border:'1 green solid';");
appendInfo("Type", Type);
if
(usercodeindex >= 0)
{
ErrorFrame ef = LastUserFrame;
appendInfo("Last user code", ef.FullMethodSignature + " (Line " + ef.Line + " in '" + ef.FileName + "')");
}
appendExceptionHeader();
if
(frames.Length > 0)
{
appendTitle("Stack");
sb.Append("<span style='margin-left:20;font-size:smaller'>Legend: ");
for
(int i = 0; i < 2; i++)
{
sb.Append("<span style=\"background-color:")
.Append(i == 0 ? systemcodecolor : usercodecolor)
.Append("border='1 black solid';margin-left:15\">")
.Append(i == 0 ? "No debug information" : "With debug information")
.Append("");
}
sb.Append("");
openBlock("margin-left:40;font-size:smaller");
foreach
(ErrorFrame ef in frames)
{
appendStack(ef);
}
closeBlock();
}
else
sb.Append("--No StackTrace available--");
closeBlock();
}
protected
void appendTitle(string Name)
{
sb.Append("<B>").Append(Name).Append(": ");
}
protected
void appendInfo(string Name, string Value)
{
appendTitle(Name);
sb.Append("<SPAN style='position:relative;left:20'>")
.Append(Value).Append("<BR>");
}
protected
void appendHR()
{
sb.Append("<HR>");
}
protected
void appendBR()
{
sb.Append("<BR>");
}
void appendStack(ErrorFrame ef)
{
openBlock("border:'1 solid black';background-color:"
+ (ef.IsUserCode ? usercodecolor : systemcodecolor)
);
sb.Append("<span style='font-size:larger;font-style:italic;color:olive'><U>");
appendInfo("Method", ef.MethodName);
sb.Append("");
appendInfo("NameSpace", ef.NameSpace);
appendInfo("Full Signature", ef.MethodSignature);
if (ef.IsUserCode)
{
appendTitle("File");
sb.Append("<A href='")
.Append(ef.FileName).Append("'>").Append(ef.FileName).Append("")
.Append("<BR>");
appendInfo("Line", ef.Line.ToString());
appendTitle("Code snippet");
openBlock("border='1 black dotted';margin-left=25");
ef.AppendCode(sb, CodeSnippetExtraLines);
closeBlock();
}
closeBlock();
}
/// <summary>
///
Only applies when getting html text. This is the number of lines on
///
each side of the offending line, that should be included in code snippets
///

public
int CodeSnippetExtraLines = 3;
#endregion
public override string ToString()
{
return Exception.ToString();
}
#region IO
/// <summary>
///
Shows the exception in html format by outputting to a default file first
///
and then opening it with the default browser.
///
(a form could have been used to do this, but tried to keep this info
///
class usable for Console applications as well)
///

public
void ShowException()
{
ShowExceptionFile("default");
}
/// <summary>
///
Shows the exception in a browser, see <see cref="ShowException()"/> for more info
///

///
<param name="ex">
public
static void ShowException(Exception ex)
{
GetInfo(ex).ShowException();
}
/// <summary>
///
First outputs the file (see <see cref="CreateExceptionFile"/>), then opens
///
it with the default connected viewer.
///
NB: the file is NOT appended. Existing info will be overwritten
///
NB2: if no writing is necessary
///

///
<param name="file">
public
void ShowExceptionFile(string file)
{
ShowExceptionFile(file, false);
}
///
<summary>
///
Same as <see cref="ShowExceptionFile(string)"/>, but with the option
///
to choose whether to append or not
///

///
<param name="file">
///
<param name="append">
public
void ShowExceptionFile(string file, bool append)
{
CreateExceptionFile(file, append);
ShowFile(file);
}
/// <summary>
///
All this does is run the file (using System.Diagnostics.Process.Start)
///
If an exception file is to be shown, make sure it is created first, or
///
use <see cref="ShowExceptionFile"/> to create and show the file instead
///
of this method
///

///
<param name="file">
public
void ShowFile(string file)
{
CheckFile(ref file);
Process
.Start(file);
}
/// <summary>
///
Appends complete directory information to a name (see code for details ;-) )
///

///
<param name="file">
public
void CheckFile(ref string file)
{
FileInfo fi = new FileInfo(file);
if
(fi.FullName.Length > file.Length)
{
//no directory provided -> use application directory
string
dir =
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
+ "\\Exceptions"
+ "\\" + AppDomain.CurrentDomain.FriendlyName
+ "\\";
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
file = dir + file;
}
if
(fi.Extension.Length == 0) file += ".htm";
}
/// <summary>
///
Outputs the exception to the specified file. Make sure the extension can
///
be read by explorer. If no extension is provided, ".htm" is used
///

///
<param name="file">
///
<param name="append">
public
void CreateExceptionFile(string file, bool append)
{
CheckFile(ref file);
using
(StreamWriter sw = new
StreamWriter(file, append, System.Text.Encoding.ASCII))
sw.Write(GetTotalMessage());
}
#endregion
}
public class SqlExceptionInfo : ExceptionInfo
{
public new readonly SqlException Exception;
public
SqlExceptionInfo(SqlException ex)
: base(ex)
{
Exception = ex;
}


#region HTML
const string AnchorSQL = "sqlinfo";
protected override void appendExceptionHeader()
{
base.appendTop();
appendTitle("SQL Errors (" + Exception.Errors.Count + ")");
openBlock("border:'1 gray solid';font-size:0.6em;margin-left:40;margin-right:20");
for
(int i = 0; i < Exception.Errors.Count; i++)
{
appendSQLError(i);
}
closeBlock();
}
protected
override void appendHeaderBottom()
{
base.appendHeaderBottom();
openHeaderLink(AnchorSQL);
sb.Append("SQL info").Append("");
}

void appendSQLError(int index)
{
if (index > 0) appendHR();
SqlError
se = Exception.Errors[index];
appendInfo("SQL class (severity)", se.Class.ToString());
appendInfo("Server", se.Server);
appendInfo("T-sql line number:", se.LineNumber.ToString());
appendInfo("Message", se.Message);
appendInfo("Procedure", se.Procedure);
appendInfo("Source", se.Source);
appendInfo("Sql error number", se.Number.ToString());
}
#endregion
}
public class ReflectionTypeLoadExceptionInfo : ExceptionInfo
{
public readonly ReflectionTypeLoadException ReflectionTypeLoadException;
public
ReflectionTypeLoadExceptionInfo(ReflectionTypeLoadException Exception)
: base(Exception)
{
this.ReflectionTypeLoadException = Exception;
foreach
(Exception e in ReflectionTypeLoadException.LoaderExceptions)
{
try
{
AddExtraInfo(GetInfo(e));
}
catch
(Exception ex)
{
AddExtraInfo(new TextInfo("Error obtaining exception info: " + ex.Message));
AddExtraInfo(new TextInfo(e.Message));
}
}
}
}
/// <summary>
///
Wrapper around a <see cref="StackFrame"/>. Not that much added functionality, but
///
some properties instead of methods to be able to use easy databinding
///

public
class ErrorFrame
{
public readonly StackFrame Base;
public
ErrorFrame(StackFrame frame)
{
Base = frame;
}
public int Line
{
get { return Base.GetFileLineNumber(); }
}
public
string FileName
{
get { return Base.GetFileName(); }
}
public string MethodName
{
get { return Base.GetMethod().Name; }
}
public string MethodSignature
{
get { return Base.GetMethod().ToString(); }
}
public string FullMethodSignature
{
get { return NameSpace + " - " + MethodSignature; }
}
public string NameSpace
{
get { return Base.GetMethod().ReflectedType.FullName; }
}
public bool IsUserCode
{
get { return Line > 0; }
}
public MethodBase GetMethod()
{
return Base.GetMethod();
}
public string GetCode()
{
return GetCode(2);
}
///
<summary>
///

///

///
<param name="Lines">indicates the amount of lines before and after the Errorline to show
public
string GetCode(int Lines)
{
if (!IsUserCode)
return "No code available for a system method";
string file = FileName;
if
(file == null || !File.Exists(file))
return "File not found!";
try
{
return AppendCode(new StringBuilder(), Lines).ToString();
}
catch
(Exception ex)
{
return "Error obtaining code information: " + ex.Message;
}
}
internal StringBuilder AppendCode(StringBuilder sb, int Lines)
{
int line = Line, from = line - Lines, to = line + Lines, curline = 0; ;
int
len = sb.Length;
const
string space = "&nbsp;";
string
tab = null;
for
(int tabcount = 0; tabcount < 4; tabcount++)
{
tab += space;
}
StreamReader
sr = null;
try

{
sr = new StreamReader(FileName);
string
l;
while
((l = sr.ReadLine()) != null)
{
if (++curline >= from)
{
if (curline > to) break;
if
(line == curline) sb.Append("<B>");
sb.Append(l.Replace("\t", tab).Replace(" ", space));
if
(line == curline) sb.Append("");
sb.Append("<BR>");
}
}
}
catch
(Exception ex)
{
sb.Length = len;
sb.Append("Error obtaining code information: ")
.Append(ex.Message);
}
finally

{
if (sr != null) sr.Close();
}
return
sb;
}
}
public interface IExtraInfo
{
void AppendHTML(StringBuilder sb, ExceptionInfo ei);
}
}
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Reflection;
using
System.Diagnostics;
using
System.Data.SqlClient;
using
System.IO;

namespace
Subro.Exceptions
{
public class ExceptionInfo : IExtraInfo
{
/// <summary>
///
The original Exception where this info object is based on
///

public
readonly Exception Exception;
public
ExceptionInfo(Exception ex)
{
this.Exception = ex;
//get stack trace information
StackTrace
st = new StackTrace(Exception, true);
frames = new ErrorFrame[st.FrameCount];
for
(int i = 0; i < frames.Length; i++)
{
frames[i] = new ErrorFrame(st.GetFrame(i));
if
(usercodeindex == -1 && frames[i].IsUserCode)
usercodeindex = i;
}
//determine total exception count
while
(ex != null)
{
exceptioncount++;
ex = ex.InnerException;
}
}
#region General properties
///
<summary>
///
The date/time of the exception. This is not the time the exception
///
was thrown, but rather when the ExceptionInfo object was created.
///
For most debugging purposes that difference in time does not matter, but
///
when the exact time is required, do not rely on this value!
///

public
readonly DateTime DateTime = DateTime.Now;
ErrorFrame[] frames;
///
<summary>
///
Gets the different StackFrames that led to this
///

public
ErrorFrame[] Frames
{
get { return frames; }
}
public int FrameCount
{
get { return frames.Length; }
}
int usercodeindex = -1;
///
<summary>
///
Returns the index of the first frame that occured in user code (and thus
///
the first frame that is actually debuggable ;-) )
///

public
int UserFrameIndex
{
get { return usercodeindex; }
}

/// <summary>
///
Information about the last step before this error occured. The returned
///
object contains information about that step, such as the method that was running.
///
When running in debug, the pdb files also provide info on the original code filename and line number
///

public
ErrorFrame LastFrame
{
get
{
if (frames.Length > 0) return frames[0];
return
null;
}
}
/// <summary>
///
Where <see cref="LastFrame"/> returns the last step in general, this property
///
returns the last step in non-system code
///
<seealso cref="UserFrameIndex"/>
///

public
ErrorFrame LastUserFrame
{
get
{
if (usercodeindex == -1) return null;
return
frames[usercodeindex];
}
}
/// <summary>
///
The exception type
///

public
string Type
{
get { return Exception.GetType().Name; }
}
#endregion
#region Mail NB: framework specific
///
This mail section was created in .net 2.0, you might
///
want to remove this part or alter it if using earlier versions
/// <summary>
///
Sends an email to the specified address
///

///
<param name="To">
public
void Send(string To)
{
System.Net.Mail.MailMessage m = GetExceptionMail();
m.To.Add(To);
System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient("mail");
client.Send(m);
}
/// <summary>
///
Send the exception info (in html format) to the addressee.
///
NB, some presumtions were made when trying to create a quick and
///
dirty send method. Safest way it to use <see cref="FillMailMessage"/> or
///
<see cref="GetExceptionMail"/> and do the sending manually
///

///
<param name="ex">
///
<param name="To">
public
static void Send(Exception ex, string To)
{
GetInfo(ex).Send(To);
}
/// <summary>
///
Gets a mailmessage instance containing the Exception info.
///
The from address is tried to be set to a custom address. If this
///
fails (or if you want to set a custom one), you'll have to set the From
///
address manually.
///

///
<returns>
public
static System.Net.Mail.MailMessage GetExceptionMail(Exception ex)
{
return GetInfo(ex).GetExceptionMail();
}
System.Net.Mail.MailMessage GetExceptionMail()
{
System.Net.Mail.MailMessage m = new System.Net.Mail.MailMessage();
FillMailMessage(m);
try

{
m.From = new System.Net.Mail.MailAddress("Exceptions@" + Environment.UserDomainName);
}
catch
{ }
return
m;
}
/// <summary>
///
Sets the body of the message to hold the exeption info
///

///
<param name="m">
public
void FillMailMessage(System.Net.Mail.MailMessage m)
{
m.IsBodyHtml = true;
m.Body = GetTotalMessage();
}
#endregion
#region ExtraInfo NB
/// <summary>
///
This class can hold extra info for an <see cref="ExceptionInfo"/>
///
Its main purpose is to output this extra info to the html text
///

public
abstract class ExtraInfo : IExtraInfo
{
/// <summary>
///
With this method, the extra info writes itself (in html format) to
///
the stringbuilder
///

///
<param name="sb">
public
abstract void AppendHTML(StringBuilder sb, ExceptionInfo ei);
public static implicit operator ExtraInfo(string text)
{
return new TextInfo(text);
}
}
public class ValuesInfo : ExtraInfo
{
List<ValueEntry> values = new List<ValueEntry>();
class
ValueEntry
{
public string Name, Text;
}
public
string Header;
public void AddText(string text)
{
AddValue(null, text);
}
public
void AddValue(string Name, string Value)
{
ValueEntry v = new ValueEntry();
v.Name = Name;
v.Text = Value;
values.Add(v);
}
public override void AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
ei.openBlock(1, 1);
if
(Header != null)
sb.Append("<b><i>").Append(Header).Append(":");
if (values.Count > 0)
{
ei.openBlock(1, 1);
foreach
(ValueEntry ve in values)
{
if (ve.Name == null)
sb.Append(ve.Text);
else
ei.appendInfo(ve.Name, ve.Text);
}
ei.closeBlock();
}
ei.closeBlock();
}
}
/// <summary>
///
Adds plain text as extrainfo
///

public
class TextInfo : ExtraInfo
{
public string Text;
public
TextInfo(string text) { Text = text; }
public
override void AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
sb.Append(Text);
}
}
#region SQL extra info
/// <summary>
///
Used to display the history of sql strings.
///

public
class SQlStringInfo : ExtraInfo
{
List<string> list = new List<string>();
public
void Add(string sql)
{
list.Add(sql);
}
public void AddRange(IEnumerable<string> values)
{
list.AddRange(values);
}
public override void AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
ei.openBlock(1);
sb.Append("<B>Recent sql strings:");
ei.openBlock("border:'gray 1 solid';margin-left:15");
int
i = 0;
foreach
(var sql in list)
{
sb.Append("<SPAN><B>--")
.Append(i++ + 1)
.Append("--<span style='margin-left:10'>")
.Append(sql)
.Append("<BR>");
}
ei.closeBlock();
ei.closeBlock();
}
}
/// <summary>
///
Add one or more sql strings to the list to be displayed in the recent
///
sql list.
///
This is usefull when you keep a list of recent executed sql strings somewhere
///
and want to include them in the exception output
///

///
<param name="SQL">
public
void AddSQL(IEnumerable<string> SQL)
{
if (sqls == null)
{
sqls = new SQlStringInfo();
AddExtraInfo(sqls);
}
sqls.AddRange(SQL);
}
SQlStringInfo sqls;
#endregion
/// <summary>
///
Add <see cref="ExtraInfo"/>. You can also use a string as parameter
///

///
<param name="ei">
public
void AddExtraInfo(IExtraInfo ei)
{
if (extrainfo == null) extrainfo = new List<IExtraInfo>();
extrainfo.Add(ei);
}
List<IExtraInfo> extrainfo;
/// <summary>
///
returns the amount of <see cref="ExtraInfo"/> objects added
///

public
int ExtraInfoCount
{
get
{
if (extrainfo == null) return 0;
return
extrainfo.Count;
}
}
#region HTML
const
string ExtraInfoAnchor = "ExtraInfo";
protected
virtual void appendExtraInfo()
{
if (extrainfo != null)
{
appendBR();
appendHR();
openBlock(2);
sb.Append("<B><A name='").Append(ExtraInfoAnchor)
.Append("'>Extra Info<BR><HR>");
openBlock("margin-left:10;font-size:smaller");
foreach
(IExtraInfo ei in extrainfo)
{
ei.AppendHTML(sb, this);
}
closeBlock();
closeBlock();
}
}
#endregion
#endregion
#region Static
///
<summary>
///
Rather than creating a new instance manually, use
///
this method to choose the proper exception object
///

///
<param name="ex">
///
<returns>
public
static ExceptionInfo GetInfo(Exception ex)
{
if (ex == null) return null;
if
(ex is SqlException) return new SqlExceptionInfo(ex as SqlException);
if
(ex is ReflectionTypeLoadException) return new ReflectionTypeLoadExceptionInfo(ex as ReflectionTypeLoadException);
return
new ExceptionInfo(ex);
}
public
static implicit operator ExceptionInfo(Exception ex)
{
return GetInfo(ex);
}
#endregion
#region Inner Exceptions
int
exceptioncount;
///
<summary>
///
returns the total number of exceptions (Main Exception + all inner exceptions)
///

public
int ExceptionCount
{
get { return exceptioncount; }
}
#endregion
#region html information
///
BEWARE: this code is manufactured to quickly create the html information
///
and is not nicely constructed for reusability.
///
A bit more friendly code for html is used in the AutoFormatter (http://blogs.vbcity.com/hotdog/archive/2005/12/30/5759.aspx) ,
///
but did not use that here to keep the code portable for fresh applications
/// <summary>
///
Gets information about this exception and all inner exceptions in HTML format
///

///
<returns>
public
string GetTotalMessage()
{
return AppendMessage(new StringBuilder()).ToString();
}
/// <summary>
///
This stringbuilder is only used in the html functions
///

protected
StringBuilder sb;
protected
void openBlock()
{
openBlock(null);
}
protected
void openBlock(int Border)
{
openBlock(Border, 0);
}
protected
void openBlock(int Border, int Indent)
{
openBlock("Border='" + Border + "px black solid'"
+ (Indent > 0 ? "margin-left=" + (Indent * 20) : null)
);
}
protected
void openBlock(string Style)
{
openclose(false, Style);
}
protected
void closeBlock()
{
openclose(true, null);
}
void
openclose(bool close, string Style)
{
sb.Append("<");
if
(close) sb.Append("/");
sb.Append("DIV");
if
(Style != null)
sb.Append(" Style=\"").Append(Style).Append("\"");
sb.Append(">");
}

public
StringBuilder AppendMessage(StringBuilder builder)
{
sb = builder;
openBlock(2);
appendTop();
appendInfo("Exception info created on", DateTime.ToString());
appendInfo("Application", AppDomain.CurrentDomain.FriendlyName);
openBlock("font-size:smaller;margin-left:20");
appendHeaderBottom();
closeBlock();
Exception
ex = Exception;
int
depth = 0;
while
(append(ex, depth++)) { ex = ex.InnerException; }
appendExtraInfo();
appendBottom();
closeBlock();
sb = null;
return
builder;
}
void appendMessage()
{
}
void IExtraInfo.AppendHTML(StringBuilder sb, ExceptionInfo ei)
{
this.sb = sb;
openBlock(2, 1);
appendTitle("Exception Info");
try

{
AppendMessage(sb);
}
catch
(Exception ex)
{
appendTitle("Error obtaining exception info: " + ex.Message);
}
closeBlock();
}

/// <summary>
///
Gives inheriting classes the possibility to append html text at the top
///
of the info block, inside the main border
///

protected
virtual void appendTop()
{
}
///
<summary>
///
Gives inheriting classes the possibility to append html inside the header
///
of the info block.
///
The base functionality adds links to the main and inner exceptions (if there are any)
///

protected
virtual void appendHeaderBottom()
{
if (exceptioncount > 1)
{
for (int i = 0; i < exceptioncount; i++)
{
openHeaderLink(AnchorNamePrefix + i);
AppendExceptionTitle(i);
sb.Append("");
}
}
if
(extrainfo != null)
{
openHeaderLink(ExtraInfoAnchor);
sb.Append("Extra Info");
}
}
/// <summary>
///
Gives inheriting classes the possibility to append html inside the header
///
of the Exception info block.        
///

protected
virtual void appendExceptionHeader()
{
}
/// <summary>
///
Used to add links in the header
///

///
<param name="href">
///
<param name="name">
protected
virtual void openHeaderLink(string href)
{
sb.Append("<A style='margin-left:15' href='#").Append(href)
   .Append("'>");
}


/// <summary>
///
Gives inheriting classes the possibility to append html text at the
///
end of the exception info, but before the main block is closed
///

protected
virtual void appendBottom()
{
}
/// <summary>
///
The prefix of the name that is added per depth so that code can
///
point directly to one of the inner exceptions
///
The Name for the first exception is ExDepth0 , the second ExDepth1 and so forth.
///
Pointing to it in a href is subseqeuntly done with href = "#ExDepthX" where X is the inner exception index
///

public
const string AnchorNamePrefix = "ExDepth";
void AppendExceptionTitle(int depth)
{
if (depth == 0)
sb.Append("Main Exception");
else
sb.Append("Inner Exception [").Append(depth).Append("]");
}
bool append(Exception ex, int depth)
{
if (ex == null) return false;
appendHR();
//add anchor information

sb.Append("<A NAME=\"").Append(AnchorNamePrefix).Append(depth)
.Append("\" style='font-size:smaller'>");
AppendExceptionTitle(depth);
sb.Append(": <span style='background-color:#990000;color:white;font-weight:bolder;width:100%'>")
.Append(ex.Message)
.Append("");
openBlock(1, 1);
if
(depth > 0)
{
ExceptionInfo ei = GetInfo(ex);
ei.sb = sb;
ei.appendHeader();
try

{
ei.appendExtraInfo();
}
catch
(Exception ex2)
{
appendTitle("Error writing extra info: " + ex2.Message);
}
}
else
appendHeader();
closeBlock();
return
true;
}
const string
usercodecolor = "lightblue",
systemcodecolor = "beige";
void appendHeader()
{
openBlock("border:'1 green solid';");
appendInfo("Type", Type);
if
(usercodeindex >= 0)
{
ErrorFrame ef = LastUserFrame;
appendInfo("Last user code", ef.FullMethodSignature + " (Line " + ef.Line + " in '" + ef.FileName + "')");
}
appendExceptionHeader();
if
(frames.Length > 0)
{
appendTitle("Stack");
sb.Append("<span style='margin-left:20;font-size:smaller'>Legend: ");
for
(int i = 0; i < 2; i++)
{
sb.Append("<span style=\"background-color:")
.Append(i == 0 ? systemcodecolor : usercodecolor)
.Append("border='1 black solid';margin-left:15\">")
.Append(i == 0 ? "No debug information" : "With debug information")
.Append("");
}
sb.Append("");
openBlock("margin-left:40;font-size:smaller");
foreach
(ErrorFrame ef in frames)
{
appendStack(ef);
}
closeBlock();
}
else
sb.Append("--No StackTrace available--");
closeBlock();
}
protected
void appendTitle(string Name)
{
sb.Append("<B>").Append(Name).Append(": ");
}
protected
void appendInfo(string Name, string Value)
{
appendTitle(Name);
sb.Append("<SPAN style='position:relative;left:20'>")
.Append(Value).Append("<BR>");
}
protected
void appendHR()
{
sb.Append("<HR>");
}
protected
void appendBR()
{
sb.Append("<BR>");
}
void appendStack(ErrorFrame ef)
{
openBlock("border:'1 solid black';background-color:"
+ (ef.IsUserCode ? usercodecolor : systemcodecolor)
);
sb.Append("<span style='font-size:larger;font-style:italic;color:olive'><U>");
appendInfo("Method", ef.MethodName);
sb.Append("");
appendInfo("NameSpace", ef.NameSpace);
appendInfo("Full Signature", ef.MethodSignature);
if (ef.IsUserCode)
{
appendTitle("File");
sb.Append("<A href='")
.Append(ef.FileName).Append("'>").Append(ef.FileName).Append("")
.Append("<BR>");
appendInfo("Line", ef.Line.ToString());
appendTitle("Code snippet");
openBlock("border='1 black dotted';margin-left=25");
ef.AppendCode(sb, CodeSnippetExtraLines);
closeBlock();
}
closeBlock();
}
/// <summary>
///
Only applies when getting html text. This is the number of lines on
///
each side of the offending line, that should be included in code snippets
///

public
int CodeSnippetExtraLines = 3;
#endregion
public override string ToString()
{
return Exception.ToString();
}
#region IO
/// <summary>
///
Shows the exception in html format by outputting to a default file first
///
and then opening it with the default browser.
///
(a form could have been used to do this, but tried to keep this info
///
class usable for Console applications as well)
///

public
void ShowException()
{
ShowExceptionFile("default");
}
/// <summary>
///
Shows the exception in a browser, see <see cref="ShowException()"/> for more info
///

///
<param name="ex">
public
static void ShowException(Exception ex)
{
GetInfo(ex).ShowException();
}
/// <summary>
///
First outputs the file (see <see cref="CreateExceptionFile"/>), then opens
///
it with the default connected viewer.
///
NB: the file is NOT appended. Existing info will be overwritten
///
NB2: if no writing is necessary
///

///
<param name="file">
public
void ShowExceptionFile(string file)
{
ShowExceptionFile(file, false);
}
///
<summary>
///
Same as <see cref="ShowExceptionFile(string)"/>, but with the option
///
to choose whether to append or not
///

///
<param name="file">
///
<param name="append">
public
void ShowExceptionFile(string file, bool append)
{
CreateExceptionFile(file, append);
ShowFile(file);
}
/// <summary>
///
All this does is run the file (using System.Diagnostics.Process.Start)
///
If an exception file is to be shown, make sure it is created first, or
///
use <see cref="ShowExceptionFile"/> to create and show the file instead
///
of this method
///

///
<param name="file">
public
void ShowFile(string file)
{
CheckFile(ref file);
Process
.Start(file);
}
/// <summary>
///
Appends complete directory information to a name (see code for details ;-) )
///

///
<param name="file">
public
void CheckFile(ref string file)
{
FileInfo fi = new FileInfo(file);
if
(fi.FullName.Length > file.Length)
{
//no directory provided -> use application directory
string
dir =
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
+ "\\Exceptions"
+ "\\" + AppDomain.CurrentDomain.FriendlyName
+ "\\";
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
file = dir + file;
}
if
(fi.Extension.Length == 0) file += ".htm";
}
/// <summary>
///
Outputs the exception to the specified file. Make sure the extension can
///
be read by explorer. If no extension is provided, ".htm" is used
///

///
<param name="file">
///
<param name="append">
public
void CreateExceptionFile(string file, bool append)
{
CheckFile(ref file);
using
(StreamWriter sw = new
StreamWriter(file, append, System.Text.Encoding.ASCII))
sw.Write(GetTotalMessage());
}
#endregion
}
public class SqlExceptionInfo : ExceptionInfo
{
public new readonly SqlException Exception;
public
SqlExceptionInfo(SqlException ex)
: base(ex)
{
Exception = ex;
}


#region HTML
const string AnchorSQL = "sqlinfo";
protected override void appendExceptionHeader()
{
base.appendTop();
appendTitle("SQL Errors (" + Exception.Errors.Count + ")");
openBlock("border:'1 gray solid';font-size:0.6em;margin-left:40;margin-right:20");
for
(int i = 0; i < Exception.Errors.Count; i++)
{
appendSQLError(i);
}
closeBlock();
}
protected
override void appendHeaderBottom()
{
base.appendHeaderBottom();
openHeaderLink(AnchorSQL);
sb.Append("SQL info").Append("");
}

void appendSQLError(int index)
{
if (index > 0) appendHR();
SqlError
se = Exception.Errors[index];
appendInfo("SQL class (severity)", se.Class.ToString());
appendInfo("Server", se.Server);
appendInfo("T-sql line number:", se.LineNumber.ToString());
appendInfo("Message", se.Message);
appendInfo("Procedure", se.Procedure);
appendInfo("Source", se.Source);
appendInfo("Sql error number", se.Number.ToString());
}
#endregion
}
public class ReflectionTypeLoadExceptionInfo : ExceptionInfo
{
public readonly ReflectionTypeLoadException ReflectionTypeLoadException;
public
ReflectionTypeLoadExceptionInfo(ReflectionTypeLoadException Exception)
: base(Exception)
{
this.ReflectionTypeLoadException = Exception;
foreach
(Exception e in ReflectionTypeLoadException.LoaderExceptions)
{
try
{
AddExtraInfo(GetInfo(e));
}
catch
(Exception ex)
{
AddExtraInfo(new TextInfo("Error obtaining exception info: " + ex.Message));
AddExtraInfo(new TextInfo(e.Message));
}
}
}
}
/// <summary>
///
Wrapper around a <see cref="StackFrame"/>. Not that much added functionality, but
///
some properties instead of methods to be able to use easy databinding
///

public
class ErrorFrame
{
public readonly StackFrame Base;
public
ErrorFrame(StackFrame frame)
{
Base = frame;
}
public int Line
{
get { return Base.GetFileLineNumber(); }
}
public
string FileName
{
get { return Base.GetFileName(); }
}
public string MethodName
{
get { return Base.GetMethod().Name; }
}
public string MethodSignature
{
get { return Base.GetMethod().ToString(); }
}
public string FullMethodSignature
{
get { return NameSpace + " - " + MethodSignature; }
}
public string NameSpace
{
get { return Base.GetMethod().ReflectedType.FullName; }
}
public bool IsUserCode
{
get { return Line > 0; }
}
public MethodBase GetMethod()
{
return Base.GetMethod();
}
public string GetCode()
{
return GetCode(2);
}
///
<summary>
///

///

///
<param name="Lines">indicates the amount of lines before and after the Errorline to show
public
string GetCode(int Lines)
{
if (!IsUserCode)
return "No code available for a system method";
string file = FileName;
if
(file == null || !File.Exists(file))
return "File not found!";
try
{
return AppendCode(new StringBuilder(), Lines).ToString();
}
catch
(Exception ex)
{
return "Error obtaining code information: " + ex.Message;
}
}
internal StringBuilder AppendCode(StringBuilder sb, int Lines)
{
int line = Line, from = line - Lines, to = line + Lines, curline = 0; ;
int
len = sb.Length;
const
string space = "&nbsp;";
string
tab = null;
for
(int tabcount = 0; tabcount < 4; tabcount++)
{
tab += space;
}
StreamReader
sr = null;
try

{
sr = new StreamReader(FileName);
string
l;
while
((l = sr.ReadLine()) != null)
{
if (++curline >= from)
{
if (curline > to) break;
if
(line == curline) sb.Append("<B>");
sb.Append(l.Replace("\t", tab).Replace(" ", space));
if
(line == curline) sb.Append("");
sb.Append("<BR>");
}
}
}
catch
(Exception ex)
{
sb.Length = len;
sb.Append("Error obtaining code information: ")
.Append(ex.Message);
}
finally

{
if (sr != null) sr.Close();
}
return
sb;
}
}
public interface IExtraInfo
{
void AppendHTML(StringBuilder sb, ExceptionInfo ei);
}
}
. . .

Example Usage
Code CopyHideScrollFull
Subro.Interaction.FeedbackCollection fbc = new Subro.Interaction.FeedbackCollection();
//A thread is not obligatory of course, but for batch usage the thread
//should be thread safe, this example shows that the feedback visualizer

//is thread safe

Thread
t = new Thread(delegate()
{
//shows
fbc.Add("Start");

//feedback with progress bar
Subro.Interaction.Feedback fb = fbc.Add("Working towards a known target");
int
target = 150;
fb.ProgressTarget = target;
for
(int i = 0; i <= target; i++)
{
Thread.Sleep(10);
fb.Progress = i;
}
fb = fbc.Add("busy with something");
for
(int i = 0; i < 100; i++)
{
//Show alive shows a 'pulse' line to indicate that
//the process is still busy (used when the exact progress is unknown)

fb.ShowAlive();
Thread
.Sleep(10);
//Each feedback item in turn can have 'children' attached
//this means that information you want to have accessible,

//but not necessarily shown each time can be added easily.

//each child item in turn of course can also posses child items

//when the feedback is shown, an 'i' button will appear to

//indicate there is extra information available

fb.Children.Add("Proccessed " + i.ToString());
}
//special feedback items such as excpetions are available as
//well. Exception example

fbc.Add(new Exception("If something went wrong, you would find the exception info here"));
//of course custom feedback items (inheriting from Feedback)
//could be added as well, where you can show your own custom

//info, eg, you could show your own form when the information

//button is clicked by overriding the ShowExtraInfo method

//instead of, or after,  showing, the results could also be
//saved (or mailed), eg for saving:

fbc.Save(@"c:\temp\testresults.htm");
});
t.IsBackground = true;
t.Start();
//if no feedbackform was created, the feedback would still
//be gathered, without the (relatively small) overhead of the feedback            

fbc.ShowDialog();
//other options are non modal Show and ShowHTML

//The latter outputs all feedback to a html page with the complete overview

Subro.Interaction.FeedbackCollection fbc = new Subro.Interaction.FeedbackCollection();
//A thread is not obligatory of course, but for batch usage the thread
//should be thread safe, this example shows that the feedback visualizer

//is thread safe

Thread
t = new Thread(delegate()
{
//shows
fbc.Add("Start");

//feedback with progress bar
Subro.Interaction.Feedback fb = fbc.Add("Working towards a known target");
int
target = 150;
fb.ProgressTarget = target;
for
(int i = 0; i <= target; i++)
{
Thread.Sleep(10);
fb.Progress = i;
}
fb = fbc.Add("busy with something");
for
(int i = 0; i < 100; i++)
{
//Show alive shows a 'pulse' line to indicate that
//the process is still busy (used when the exact progress is unknown)

fb.ShowAlive();
Thread
.Sleep(10);
//Each feedback item in turn can have 'children' attached
//this means that information you want to have accessible,

//but not necessarily shown each time can be added easily.

//each child item in turn of course can also posses child items

//when the feedback is shown, an 'i' button will appear to

//indicate there is extra information available

fb.Children.Add("Proccessed " + i.ToString());
}
//special feedback items such as excpetions are available as
//well. Exception example

fbc.Add(new Exception("If something went wrong, you would find the exception info here"));
//of course custom feedback items (inheriting from Feedback)
//could be added as well, where you can show your own custom

//info, eg, you could show your own form when the information

//button is clicked by overriding the ShowExtraInfo method

//instead of, or after,  showing, the results could also be
//saved (or mailed), eg for saving:

fbc.Save(@"c:\temp\testresults.htm");
});
t.IsBackground = true;
t.Start();
//if no feedbackform was created, the feedback would still
//be gathered, without the (relatively small) overhead of the feedback            

fbc.ShowDialog();
//other options are non modal Show and ShowHTML

//The latter outputs all feedback to a html page with the complete overview

. . .
© 2005 Serge Baranovsky. All rights reserved.
All feed content is property of original publisher. Designated trademarks and brands are the property of their respective owners.

This site is maintained by SubMain(), a division of vbCity.com, LLC