HotDog's Blog

Hotdog (Robert Verpalen) about C# and vb.net

This blog hosted by:
http://blogs.vbcity.com      
  Home :: Syndication  :: Login

AprMay 2008Jun
SMTWTFS
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

Articles

Archives

Topics

CONTACT

Fun but useful linkies

General

VS 2005

Wolfenstein ET

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