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();
}
elsealivecount = 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;
varindex = 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;
} }
#endregionpublic
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], '_');
}
elsedescr = "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);
}
elsefrm.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();
}
elsefrm.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 finishedupdate(
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();
}
elsefb.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 intExtraInfoOffset = 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
{
}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
{
}           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.
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 informationsb.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 =
" ";
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 informationsb.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 =
" ";
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
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
. . .