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

Friday, February 22, 2008 #

Source Code: http://blogs.vbcity.com/hotdog/archive/2008/02/22/8983.aspx

Many times I've found it useful to have a process give feedback information to find out if everything was performing as wanted. And also one should be able to visualize, save and/or mail that information if wanted. Often you only want to show the information under certain conditions (an error occured, but you want to show the entire feedback)
The classes contained here: http://blogs.vbcity.com/hotdog/archive/2008/02/22/8983.aspx , provides that generic functionality. An example on how to use can be found at the bottom.

The main collection is contained in a FeedbackCollection class instance. Each individual Feedback item in that collection can in turn contain a subcollection of children.
Screenshots will follow later when I've retrieved the password to the ftp site :p

PS, this text is a bit incomplete since I'm slightly cross because the original text, which was a lot more elaborate than this bit, was lost when I thought to simply be able to press 'post' upon which all my work was lost because apparently I wasn't logged in any more (and no, it did not restore...). And almost always I copy first, except now ;)  But will add some more info later on.

posted @ 3:54 AM | Feedback (0)

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