HotDog's Blog

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

vbCity Blogs moved to:
http://cs.vbcity.com/blogs
  Home :: Syndication  :: Login

AprMay 2012Jun
SMTWTFS
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

Articles

Archives

Topics

CONTACT

Fun but useful linkies

General

VS 2005

Wolfenstein ET

Tuesday, February 10, 2009 #

Another 'old' bit of code that I'm still using a lot none the less. Whenever I needed drag and drop, it annoyed me that I had to use repetitive code to start it manually by tracing the mousedown etc. So what better place for repetitive code than in a reusable component. It isn't any highly fancy or complicated code, but a raincoat doesn't have to be pretty to keep you dry ;)
Anyway, when I was thinking when extending the AutoDragger today to alter the behaviour when dragging from a datagridview, that it was never posted on this blog. So here it is. (As far as I know it hasn't been outdated in the sense that .net now supports this out of the box)

Usage in Designer: drop the component on your form, select a control you wish to enable auto dragging for, locate the added 'UseAutoDrag' property on that control (under a 'Drag Drop' header) and set it to true.
(Of course the component can be used as a runtime class too.) The cursor can be set in the drag events or by setting a default drag cursor on the component.

Code CopyHideScrollFull

namespace
Subro.Controls
{
using System;
using
System.ComponentModel;
using
System.Windows.Forms;
using
System.Collections.Generic;
using
System.Drawing;
/// <summary>
///
Generic component that can start dragging for most control by handling
///
its mouse events.
///
Set the Controls property to indicate for which controls dragging should be handled
///
automatically. Catch the StartDrag event to alter the data which is to be dragged.
///
</summary>
[DefaultEvent("StartDrag")]
[ProvideProperty("UseAutoDrag",typeof(Control))]
public
class AutoDragger : Component,IExtenderProvider
{
public AutoDragger()
{
}
public AutoDragger(IContainer c)
: this()
{
c.Add(this);
}
public AutoDragger(Control ctr)
: this(ctr, null)
{
}
public
AutoDragger(Control ctr, EventHandler<AutoDragEventArgs> handler)
: this()
{
Register(ctr);
if
(handler != null)
StartDrag += handler;
}
List<Control> controls = new List<Control>();
public int Register(Control DragSource)
{
int i = controls.IndexOf(DragSource);
if
(i != -1) return i;
if
(!DesignMode)
{
DragSource.MouseDown += new MouseEventHandler(Control_MouseDown);
DragSource.MouseMove += new MouseEventHandler(Control_MouseMove);
DragSource.MouseUp += new MouseEventHandler(Control_MouseUp);
DragSource.GiveFeedback += new GiveFeedbackEventHandler(Control_GiveFeedback);
}
controls.Add(DragSource);
i = controls.Count - 1;
ControlAdded(DragSource);
OnControlsChanged();            
return
i;
}

protected virtual void ControlAdded(Control c)
{
}
public void UnRegister(Control DragSource)
{
DragSource.MouseDown -= new MouseEventHandler(Control_MouseDown);
DragSource.MouseMove -= new MouseEventHandler(Control_MouseMove);
DragSource.MouseUp -= new MouseEventHandler(Control_MouseUp);
DragSource.GiveFeedback -= new GiveFeedbackEventHandler(Control_GiveFeedback);
controls.Remove(DragSource);
ControlRemoved(DragSource);
OnControlsChanged();
}
protected virtual void ControlRemoved(Control c)
{
}
public void UnRegisterAll()
{
int c = controls.Count -1;
for
(int i  = c;i>=0 ;i--)
UnRegister(controls[i]);
}
const string Category = "DragDrop";
[DefaultValue(null)]
[Category(Category)]
public
Control[] Controls
{
get
{
return controls.ToArray();
}
set

{
suspendcontrolschanged = true;
while
(controls.Count > 0)
UnRegister(controls[0]);

if
(value != null)
foreach (Control c in value)
{
Register(c);
}
suspendcontrolschanged = false;
OnControlsChanged();
}
}
bool suspendcontrolschanged;
protected
virtual void OnControlsChanged()
{
if (!suspendcontrolschanged && ControlsChanged != null) ControlsChanged(this, EventArgs.Empty);
}
public event EventHandler ControlsChanged;
public Control this[int Index]
{
get
{
return controls[Index];
}
}
/// <summary>
///
The amount of registered controls used as a dragsource
///
</summary>
public
int DragSourceCount
{
get { return controls.Count; }
}
public event EventHandler<AutoDragEventArgs> StartDrag;
private DragDropEffects effects = DragDropEffects.All;
[DefaultValue(DragDropEffects.All)]
public
DragDropEffects Effects
{
get { return effects; }
set
{ effects = value; }
}
protected override void Dispose(bool disposing)
{
UnRegisterAll();
base
.Dispose(disposing);
}
int diffmin = 2;
[DefaultValue(2)]
[Category(Category)]
public
int DifferenceMinimum
{
get { return diffmin; }
set

{
diffmin = value;
}
}

AutoDragEventArgs curdrag;

void
Control_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
curdrag = new AutoDragEventArgs(sender as Control, e.Location, this);
}
else
curdrag = null;
}
void Control_MouseUp(object sender, MouseEventArgs e)
{
if (curdrag != null)
{
AutoDragEventArgs de = curdrag;
EndCurDrag();          
OnDragEnded(de);                    
}
}
protected virtual void OnDragEnded(AutoDragEventArgs e)
{
}
void Control_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && curdrag != null)
{
curdrag.CheckStartDrag(e);
}
}

void Control_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
curdrag.SetFeedback(e);
}
private Cursor cursor;
[DefaultValue(null)]
public
Cursor DragCursor
{
get { return cursor; }
set

{
if (cursor == value) return;
cursor = value;
dragicon = null;
}
}
bool
ShouldSerializeDragCursor()
{
return cursor != null && dragicon == null;
}
private Icon dragicon;
[DefaultValue(null)]
public
Icon DragIcon
{
get { return dragicon; }
set

{
if (dragicon == value) return;
if
(value == null)
DragCursor = null;
else
DragCursor = new Cursor(value.Handle);
dragicon = value;
}
}
private bool alwaysshowcustomcursor;
[Description("Only applies when the DragCursor property is set. If this value is false, the cursor is only shown when drop is allowed")]
[DefaultValue(false)]
public
bool AlwaysShowCustomCuror
{
get { return alwaysshowcustomcursor; }
set
{ alwaysshowcustomcursor = value; }
}

void EndCurDrag()
{
if (curdrag != null)
curdrag.Dispose();
}
public class AutoDragEventArgs : EventArgs
{
public readonly Control Control;
public
readonly Point StartPoint;
public
readonly AutoDragger AutoDragger;
public
AutoDragEventArgs(Control Control, Point StartPoint, AutoDragger Owner)
{
this.Control = Control;
this
.StartPoint = StartPoint;
this
.AutoDragger = Owner;
}
private object dragobj;
public
object DragObject
{
get { return dragobj; }
set
{ dragobj = value; }
}            
private bool started;
public bool DragStarted
{
get { return started; }                
}

internal void CheckStartDrag(MouseEventArgs e)
{
int diff = Math.Abs(e.X - StartPoint.X)
+ Math.Abs(e.Y - StartPoint.Y);
if (diff >= AutoDragger.diffmin)
{
started = true;
Cursor = AutoDragger.DragCursor;
AutoDragger.OnStartDrag(this,e);
}
}
public void Dispose()
{
if (AutoDragger.curdrag == this)
AutoDragger.curdrag = null;
}
private
Cursor cursor;
public Cursor Cursor
{
get { return cursor; }
set

{
cursor = value;                    
}
}
internal void SetFeedback(GiveFeedbackEventArgs e)
{
if (cursor != null)
{
if (e.Effect != DragDropEffects.None || AutoDragger.alwaysshowcustomcursor)
{
e.UseDefaultCursors = false;
Cursor
.Current = cursor;
}
}
}
internal object GetDragObject()
{
Control c = Control;
if
(c is ListControl)
return (c as ListControl).SelectedValue;
if (c is TextBoxBase)
return GetDragObject(c as TextBoxBase);
if (c is Label)
return c.Text;
if (c is TreeView)
return GetDragObject(c as TreeView);
if (c is DataGridView)
return GetDragObject(c as DataGridView);
return null;
}
object GetDragObject(TextBoxBase tb)
{
if (!tb.ReadOnly)
{
//when in edit mode and selecting text, don't start dragging
//TODO: make optional    

return
null;
}
if
(tb.SelectionLength == 0)
return tb.Text;
return tb.SelectedText;
}
object GetDragObject(TreeView t)
{
TreeNode node = t.GetNodeAt(StartPoint);
if
(node == null) return null;
t.SelectedNode = node;
return
node;
}
object GetDragObject(DataGridView dg)
{
DataGridView.HitTestInfo ht = dg.HitTest(StartPoint.X, StartPoint.Y);
if
(ht.RowIndex == -1) return null;
if
(ht.ColumnIndex > -1 && !dg.Columns[ht.ColumnIndex].ReadOnly && dg[ht.ColumnIndex, ht.RowIndex].IsInEditMode)
return null; //when on a cell in edit mode, don't start drag drop
if (ht.ColumnIndex == -1 || dg.SelectionMode == DataGridViewSelectionMode.FullRowSelect || AutoDragger.AlwaysDragFullRow)
{
DataGridViewRow row = dg.Rows[ht.RowIndex];
if
(row.DataBoundItem != null) return row.DataBoundItem;
return
row;
}
return
dg[ht.ColumnIndex, ht.RowIndex].Value;
}
}
[Browsable(false)]
public
AutoDragEventArgs LastDragInfo
{
get
{
return curdrag;
}
}
protected virtual void OnStartDrag(AutoDragEventArgs e,MouseEventArgs me)
{
if (StartDrag != null)
StartDrag(this, e);
if (e.DragObject == null)
e.DragObject = GetDragObject(e);
if (e.DragObject != null)
{
e.Control.DoDragDrop(e.DragObject, Effects);
OnDragStarted(e);
}
else

{
//dragging not allowed
curdrag = null;
}
}
protected virtual void OnDragStarted(AutoDragEventArgs e)
{
}
protected virtual object GetDragObject(AutoDragEventArgs e)
{
return e.GetDragObject();
}
   

private bool fullrow;
[DefaultValue(false)]
[Description("Only applies when dragging on a datagridview.\r\nIf this value is not set, the full row only will be dragged if the selection mode of the grid is FullRowSelect or the rowheader is dragged and the cell value otherwise.")]
public
bool AlwaysDragFullRow
{
get { return fullrow; }
set
{ fullrow = value; }
}






#region IExtenderProvider Members
bool IExtenderProvider.CanExtend(object extendee)
{
return extendee is Control;
}
[DefaultValue(false)]
[Category(Category)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] //serialized in the control property
public
bool GetUseAutoDrag(Control c)
{
if (controls.Count == 0) return false;
return
controls.Contains(c);
}
public
void SetUseAutoDrag(Control c, bool value)
{
if (GetUseAutoDrag(c) == value) return;
if
(!value)
UnRegister(c);
else
Register(c);
}
#endregion
}
}
. . .
posted @ 10:12 PM | Feedback (66)

Monday, December 29, 2008 #

It's been a long time since making this class and don't believe I posted it yet, but since I'm going to use it in the DataGridViewGrouper (see previous post), thought I'd post it now.
Since it's “older” code, it's fully .net 2.0 compatible. It can be uses where ever an IComparer interface is used. (or the Compare function of the GenericComparer<> class for a Comparison<> call)

Usage example:

Code Copy
Array.Sort(YourArray, new GenericComparer());

There is a generic comparer class already in the framework, but it will fail on certain types. It's been a while, but I believe the .net native class doesn't support the Nullable struct and strong implementers of the generic IComparer<> interface. (The new Linq will fail if the object sorted on is not an IComparable)

Code CopyHideScrollFull
using System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Collections;
using
System.ComponentModel;

namespace
Subro
{
/// <summary>
///
Comparer that tries to find the 'strongest' comparer for a type.
///
if the type implements a generic IComparable, that is used.
///
otherwise if it implements a normal IComparable, that is used.
///
If neither are implemented, the ToString versions are compared.
///
INullable structures are also supported.
///
This way, the DefaultComparer can compare any object types and can be used for sorting any source.
///
</summary>
///
<example>Array.Sort(YourArray,new GenericComparer());</example>
public
class GenericComparer : IComparer
{
public GenericComparer()
{
}
public
GenericComparer(Type Type)
{
this.Type = Type;
}
Type type;
public
Type Type
{
get
{
return type;
}
set

{
if (value == null) throw new ArgumentNullException();
type = value;
comp = null;
}
}
Type targettype;
///
<summary>
///
normally the same as the type, but can be set to a different type
///
</summary>
public
Type TargetType
{
get
{
if (targettype == null) return type;
return
targettype;
}
set

{
if (TargetType == value) return;
targettype = value;
comp = null;
}
}
IComparer comp;
IComparer GetGenericComparer(Type From, Type To)
{
while (To != typeof(object))
{
if (typeof(IComparable<>).MakeGenericType(To).IsAssignableFrom(From))
return (IComparer)Activator.CreateInstance(typeof(StrongCompare<,>).MakeGenericType(From,To));
To = To.BaseType;
}
return
null;
}
public IComparer GetComparer(Type From, Type To)
{
var gen = GetGenericComparer(From,To);
if
(gen != null)
return gen;
else if (typeof(IComparable).IsAssignableFrom(type))
{
return (IComparer)Activator.CreateInstance(typeof(NonGenericCompare<>).MakeGenericType(type));
}
else
if (type.IsGenericType && typeof(Nullable<>) == type.GetGenericTypeDefinition())
{
var basetype = type.GetGenericArguments()[0];
return
(IComparer)Activator.CreateInstance(typeof(NullableComparer<>).MakeGenericType(basetype),
GetComparer(basetype, To == From ? basetype : To));
}
return
new StringComparer();
}
class NullableComparer<T>:IComparer
where T:struct
{
public readonly IComparer BaseComparer;
public
NullableComparer(IComparer BaseComparer)
{
this.BaseComparer = BaseComparer;
}
object getval(object o)
{
return ((Nullable<T>)o).Value;                
}
public int Compare(object x, object y)
{
return BaseComparer.Compare(getval(x), getval(y));   
}
}
class StrongCompare<F,T> : IComparer
where F : IComparable<T>
{
public int Compare(object x, object y)
{
return ((F)x).CompareTo((T)y);
}
}
class NonGenericCompare<T> : IComparer
where T: IComparable
{
public int Compare(object x, object y)
{
return ((T)x).CompareTo(y);
}
}
class StringComparer : IComparer
{
public int Compare(object x, object y)
{
return x.ToString().CompareTo(y.ToString());
}
}
public bool Descending
{
get
{
return factor < 0;
}
set

{
factor = value ? -1 : 1;
}
}
int factor = 1;
int compare(object x, object y)
{
if (x == y) return 0;            
if
(x == null) return -1;
if
(y == null) return 1;
if
(type == null)
Type = x.GetType();
if (comp == null)
comp = GetComparer(type, TargetType);
return comp.Compare(x, y);            
}
public int Compare(object x, object y)
{
return factor * compare(x, y);
}
}

public class GenericComparer<T> : GenericComparer,IComparer<T>
{
public GenericComparer()
: base(typeof(T))
{ }
public int Compare(T a, T b)
{
return base.Compare(a, b);
}
}


public class PropertyDescriptorComparer : GenericComparer
{
public readonly PropertyDescriptor Prop;
public PropertyDescriptorComparer(PropertyDescriptor Prop)
: this(Prop, true)
{
}
public
PropertyDescriptorComparer(PropertyDescriptor Prop, bool Descending)
: base(Prop.PropertyType)
{
this.Prop = Prop;
this
.Descending = Descending;
}
}

public static partial class Extend
{
public static void Sort<T>(this List<T> list, Func<T, T, int> Comparer)
{
list.Sort(new Comparison<T>(Comparer));
}
/// <summary>
///
Sort on a property or field
///
</summary>        
public
static void Sort<T, S>(this List<T> list, Func<T, S> Property)
{
var c = new Subro.GenericComparer<S>();
Sort(list, (t1, t2) => c.Compare(Property(t1), Property(t2)));
}
}


}
. . .
posted @ 3:56 AM | Feedback (117)

Friday, December 19, 2008 #

Complete Source Code: http://blogs.vbcity.com/hotdog/archive/2008/12/19/9225.aspx


Note: the code is now rewritten to be 2.0 compatible (the project was written in vs2008 with compatibility set on framework 2.0), but older designers may still have trouble compiling because of the use of newer syntax (such as automatic properties). All 'var' keywords should be replaced, but they tend to sneak in because they are so easy to use ;)
If the newer syntax is a problem for many of you, drop me a note and I'll try to remove all of those syntaxes too.

Example:
Assuming you have a form with a datagridview called dataGridView1, add this code (after setting its datasource):

Code CopyHideScrollFull
var grouper = new Subro.Controls.DataGridViewGrouper(dataGridView1);
grouper.SetGroupOn("NameOfThePropertyToGroupOn");
. . .

Functionality:
The DataGridViewGrouper is a component that can be added in the forms designer or in runtime to add grouping to any existing (databound) DataGridView. Grouping can be done on existing properties of the underlying data or with a custom value function. The displayed data can be altered through the exposed Display event.
Grouped rows can be quickly selected by double clicking the group header, and the rows can be collapsed and expanded by the user.

Usage:
3 simple steps
- Add the component to the designer or create it in runtime
- Set its DataGridView property to any existing databound DataGridView
-In runtime set the GroupOn property or call any of the SetGroupOn methods to start grouping

Other settings include
-the sorting of the groups (asc,desc or none, using the lists original sources order as leading)
-if the count of rows has to be shown (the shown information can be altered by catching the Display event)
-if the property name on which is grouped has to be shown (can also be altered by the Display event)
-besides the options above, the shown group value can also be altered in the Display event.

Background:
For a while now I was facing a request to add grouping to an overview in a DataGridView. And as you may have noticed: there is no grouping functionality in the default System.Windows.Forms.DataGridView.
Now there are ways of course, by adding DataGridViewRows manually and creating the groups by adding rows, but that would screw up the underlying data binding and automatic buffering/formatting/filtering/sorting, etc. Wanted to keep the bound functionality intact, but still create group rows. The solution in itself is simply to create a wrapper source that includes those rows and the grouped rows underneath and asign that to the grid, leaving the original source unaltered, and override the grouping rows painting/editing. A theory simple enough in itself, of course the implementation was a bit more work, but still less difficult than expected.
The grouping source was created easily enough, especially with the great functionality of Linq. The trick was in seperating what the grid saw from the underlying functionality. By inheriting from BindingSource all tools were available for that job. The DataSource and DataMember properties are set by the original values of the same properties in the Grid, therefor exposing the proper 'Columns'. All adding and removing had to be done in the underlying datagrid source, so those values were overridden.
The DataGridViewGrouper component itself is assigned to a grid and the Source of the grid itself is set to the grouped source. When a group row has to be painted, it is handled by the component, the other rows follow the default behaviour. Editing for a grouprow is simply cancelled. If the original source is a BindingSource, the source's positioning is synchronized.
Note that the index of the datagridview row won't be the same as the index in the original source, so you shouldn't be getting an object out of the original source directly with the row index. Then again, you should never do that anyway ;)  Use the Grid.Rows[position].DataBoundItem property instead.

Source Code

TODO:
* Now the component synchronizes navigation if the source is another BindingSource. Will look later to synchronize with any CurrencyManager.
* Adding code comments :p

posted @ 6:56 AM | Feedback (75)

Wednesday, June 04, 2008 #

First thing's first: the function :D

Code Copy

create
function Split (
@StringToSplit varchar(2048),
@Separator varchar(128))
returns table as return
with
indices as
(
select 0 S, 1 E
union all

select
E, charindex(@Separator, @StringToSplit, E) + len(@Separator)
from indices
where
E > S
)
select
substring(@StringToSplit,S,
case when E > len(@Separator) then e-s-len(@Separator) else len(@StringToSplit) - s + 1 end) String
,
S StartIndex        
from indices where S >0

 

Yeah, yeah, I know, such a function is an old topic and there are probably numerous solutions available, but I had the impression all were a bit outdated and created before the introduction of the CTE (Common Table Expression) in sql server 2005. Most examples either use a temporary table or return multiple result sets.
Ideal is the use of a function, but using a temporary table in it would prevent certain query optimizations that sql server would normally do for you (besides the small hit of creating a temptable). Granted, these days fast computers don't require that level of optimizing, but still, in heavy workloads every detail might matter ;)  In comes the 'newly' introduced CTE functionality. That might have been around for a couple of years, but I've never really used it until recenlty :$ Whatever the case, it makes a function such as split suddenly a lot shorter.
The function returns both the values (column name 'String' ) and the StartIndex. The Index might not be needed most of the time, but when joining, searching or selecting on the String, the column name is needed anyway, so the StartIndex won't be in the way, and it is there when it is needed. (As a small sidenote, in the case E > len(@Separator is used instead of E > S because the same optimizations that give the speed benefit, crashed the code on the substring() when using criteria on the string)

Some examples:

Code Copy
--simple split comma delimited string example
select
* from Split('aaa,b,cccc,dd,e,ffff,g' , ',')

--when the separator is not found, no problems either

select
* from Split('abcdefgh' , ',')

--empty values are returned too, the list would not be complete without them (for example when handling input file lines)

select
* from Split(',,a,,b,c,,,d' , ',')

--skipping the empty values would simply be using a criterium

select
* from Split(',,a,,b,c,,,d' , ',') where len(String) > 0

--of course, splitting with other (and longer) strings than a comma is supported too

select
* from Split('aaa->bb->cccc->d' , '->') where len(String) > 0

--just a final example with some query options combined

select
rtrim(ltrim(String)) from Split('from abc from abb from cdd from addd' , 'from')
where ltrim(String) like 'a%'
posted @ 1:21 AM | Feedback (53)

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 (90)

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

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

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

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

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

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

{
return FeedBackLevel.Custom;
}
}
set

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

protected
override void OnMouseMove(MouseEventArgs e)
{

Feedback
onbutton = null;

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

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

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

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

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

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

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

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

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

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

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

public
int UserFrameIndex
{
get { return usercodeindex; }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

///

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

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

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

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

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

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

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

public
int UserFrameIndex
{
get { return usercodeindex; }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

///

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

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

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

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

//is thread safe

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

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

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

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

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

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

//indicate there is extra information available

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

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

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

//button is clicked by overriding the ShowExtraInfo method

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

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

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

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

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

//is thread safe

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

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

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

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

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

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

//indicate there is extra information available

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

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

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

//button is clicked by overriding the ShowExtraInfo method

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

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

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

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

. . .
posted @ 1:09 AM | Feedback (65)

Thursday, November 08, 2007 #

Article can be found here: http://blogs.vbcity.com/hotdog/articles/8858.aspx

The new Linq functionality is great and easy to use. Enough tutorials exist on how to use the Linq syntax and on both DLinq and XLinq as well. However when you want to implement your own Linq source, pickings are slim. The ones that are available use the IQueryable interface. However, before that interface and seemingly at the heart of Linq is the 'Query Expression Pattern'
In the following article the basics of that pattern are explored and hopefully provide some insight on how to easily extend on the vastness already provided by Linq: http://blogs.vbcity.com/hotdog/articles/8858.aspx

 

posted @ 9:55 AM | Feedback (96)

Thursday, July 05, 2007 #

the System.DirectoryServices namespace from .net allready contains a lot of build in functionality for use with Active Directory. However some default task have to coded manually still. Of course that meant writing some support tools which I've been happilly using for some time now :)
The code posted here are the base classes used for those tools. At a later time if anyone is interested I'll include the tools project too (simple LDAP browser, user form that can lock/unlock, set pw and mail forwardings)

The code is wrapped around the System.DirectoryServices objects and thus need a reference to that (default .net) namespace. The static ActiveDirectory class contains methods to quickly search entries. The ActiveDirectoryEntry class is a wrapper around the .net DirectoryEntry class which encapsulates functions to quickly access properties. ADFilter makes creating queries easier and UserInfo is a class that uses the other classes to quickly get and set user info.

Example usage:

Code Copy
//get a single entry
ActiveDirectoryEntry
d = ActiveDirectory.GetEntry("srv*", "computer");
Console
.WriteLine(d["whenCreated"]);
//The default activedirectory queries can be used to find entries
//list all computers:

foreach
(ActiveDirectoryEntry de in ActiveDirectory.GetEntries("objectClass=computer"))
{
Console.WriteLine(de.Name);
}
//list all users whose password was changed in the last 2 months
string
date = ActiveDirectory.FormatDateTime( DateTime.Today.AddMonths(-2));
foreach
(ActiveDirectoryEntry de in ActiveDirectory.GetEntries("(&(objectClass=user)(whenCreated>="+date+"))"))
{
Console.WriteLine("{0}, created: {1}", de.Name, de["whenCreated"]);
}
//Another method is to use the ADFilter object, which makes writing those nasty queries somewhat easer
//Doing the same as above:

ADFilter
filter = new ADFilter();
filter.objectClass = "user";
filter.Add(new ADCriterium("whenCreated", Operator.Equals | Operator.GreaterThan,DateTime.Today.AddMonths(-2)));
Console
.WriteLine(filter);
foreach
(ActiveDirectoryEntry de in ActiveDirectory.GetEntries(filter))
{
Console.WriteLine("{0}, created: {1}", de.Name, de["whenCreated"]);
}
//for a small query as above the filter isn't so important, but imagine the more complicated
//scenario of also wanting to search for  at names beginning with w or a badlogincount of 0

ADFilter
subquery = new ADFilter();
subquery["cn"] = "w*";
subquery["badPwdCount"] = 0;
filter.Add(subquery);
//this produces the following filter:

//    (&(objectClass=user)(whenCreated>=070505000000Z)(&(cn=w*)(badPwdCount=0)))

Console
.WriteLine(filter);
foreach
(ActiveDirectoryEntry de in ActiveDirectory.GetEntries(filter))
{
Console.WriteLine("{0}, created: {1}", de.Name, de["whenCreated"]);
}

Example usage UserInfo class:

Code Copy
//getting the current user can be done with the GetCurrentUser function
UserInfo
ui = UserInfo.GetCurrentUser();
string
surname = ui.SurName;

//some other user (when using wildcard searches, the first found entry is used)

ui = new UserInfo("Hotd*");
//NB: if the user is not found, calling properties will throw an exception

// which informs that no active directory entry exists

// You can check if a valid entry was found by checking the HasActiveDirectoryEntry property

if
(!ui.HasActiveDirectoryEntry) return;
if
(ui.IsMailForwarded)
{
string mail = ui.MailForwardedTo.FullName;
}
//other functions included, for example password changing

ui.SetPassword("NewPassword", true);

The classes:

Code CopyHideScrollFull
//#define Framework30 //uncomment this line if you want to include framework 3.0 functions such as Linq

using
System;
using
System.Collections.Generic;
using
System.Text;
//needs a reference to System.DirectoryServices

using
System.DirectoryServices;
using
ad = System.DirectoryServices.ActiveDirectory;
using
System.Runtime.InteropServices;
using
System.Collections.Specialized;
#if
Framework30
using System.Linq;
using System.Linq.Expressions;

#endif

namespace
AD
{
/// <summary>
///
Base class for all returned directory entries
///
</summary>
public
class ActiveDirectoryEntry
{
public readonly DirectoryEntry Entry;
public
ActiveDirectoryEntry(string Path)
: this(new DirectoryEntry(Path))
{
}
internal
ActiveDirectoryEntry(DirectoryEntry Entry)
{
if (Entry == null)
throw new ArgumentNullException();
this.Entry = Entry;
}
public
static ActiveDirectoryEntry GetEntry(DirectoryEntry Entry)
{
if (Entry == null) return null;
return
new ActiveDirectoryEntry(Entry);
}
/*
public static implicit operator ActiveDirectoryEntry(DirectoryEntry de)
{
if (de == null) return null;
return new ActiveDirectoryEntry(de);
}*/

/*
public static implicit operator DirectoryEntry(ActiveDirectoryEntry de)
{
return de.Entry;
}*/

public string Path
{
get { return Entry.Path; }
}
public void Invoke(string methodName, params object[] args)
{
Entry.Invoke(methodName, args);
}
public
object InvokeGet(string methodName)
{
return Entry.InvokeGet(methodName);
}
public
void InvokeSet(string methodName, params object[] args)
{
Entry.InvokeSet(methodName, args);
}
public void CommitChanges()
{
Entry.CommitChanges();
}
public
void RefreshCache()
{
Entry.RefreshCache();
}
ActiveDirectoryEntry parent;
public
ActiveDirectoryEntry Parent
{
get
{
if (parent == null)
parent = new ActiveDirectoryEntry(Entry.Parent);
return parent;
}
}
const string memberof = "memberof";
const
string members = "member";
public
IEnumerable<ActiveDirectoryEntry> EnumerateMemberOf()
{
foreach (string s in Properties[memberof])
{
yield return GetEntry(s);
}
ActiveDirectoryEntry
e = GetPrimaryGroup();
if
(e != null)
yield return e;
}
internal bool IsMemberOf(string Group)
{
object o = this[memberof];
if
(o == null) return false;
IEnumerable
<object> owners = o is string ? new string[] { (string)o } : (object[])o;
Group = "CN=" + Group;
foreach
(string s in owners)
{
if (s.Contains(Group)) return true;
}
return
false;
}
public bool IsMemberOf(ActiveDirectoryEntry group)
{
foreach (ActiveDirectoryEntry ad in EnumerateMemberOf())
{
if (ad.Equals(group)) return true;
}
return
false;
}
public AccessType GetAccessType(ActiveDirectoryEntry group)
{
List<ActiveDirectoryEntry> list = GetMemberOf();
foreach
(ActiveDirectoryEntry ad in list)
{
if (ad.Equals(group)) return AccessType.Direct;
}
foreach
(ActiveDirectoryEntry ad in list)
{
AccessType at = ad.GetAccessType(group);
if
(at != AccessType.None)
return AccessType.Nested;
}
return
AccessType.None;
}
public string Prefix
{
get
{
return Path.Remove(Path.IndexOf("//") + 2);
}
}
ActiveDirectoryEntry GetEntry(string s)
{
string path = Prefix + Clean(s);
return
new ActiveDirectoryEntry(path);
}
string
Clean(string s)
{
if (s.IndexOf('/') >= 0)
{
s = s.Replace("/", @"\/");
}
return
s;
}
public bool IsTop
{
get
{
return Entry.Parent.SchemaClassName == "domainDNS";
}
}
public bool IsEndpoint
{
get
{
switch (Class)
{
case DefaultClass.organizationalUnit:
case
DefaultClass.container:
return false;
default:
return true;
}
}
}
public string SchemaClassName
{
get { return Entry.SchemaClassName; }
}
public DefaultClass Class
{
get
{
try
{
return (DefaultClass)Enum.Parse(typeof(DefaultClass), Entry.SchemaClassName);
}
catch

{
return DefaultClass.any;
}
}
}
public List<ActiveDirectoryEntry> GetMemberOf()
{
List<ActiveDirectoryEntry> res = new List<ActiveDirectoryEntry>();
foreach
(ActiveDirectoryEntry de in EnumerateMemberOf())
{
res.Add(de);
}
return
res;
}
public PropertyValueCollection MemberOf
{
get { return Properties[memberof]; }
}
public PropertyValueCollection Members
{
get { return Properties[members]; }
}
public IEnumerable<ActiveDirectoryEntry> GetMembers()
{
foreach (string s in Members)
{
yield return GetEntry(s);
}
System.Security.Principal.SecurityIdentifier sid = GetSID();
if
(sid != null)
{
string s = sid.ToString();
int
i = s.LastIndexOf('-');
if
(i != -1)
{
s = s.Substring(++i);
//byte[] b = GetSIDArray();
ADFilter filter = new ADFilter("primarygroupID", s);
filter.ObjectClass = DefaultClass.user;
foreach (ActiveDirectoryEntry ad in filter.GetEntries())
{
//if(IsEqual(b,ad.GetSIDArray(),b.Length-4))
yield
return ad;
}
}
}
}
bool IsEqual(byte[] b1, byte[] b2, int len)
{
for (int i = 0; i < len; i++)
{
if (b1[i] != b2[i]) return false;
}
return
true;
}
public IEnumerable<ActiveDirectoryEntry> DeepEnumerateMembers()
{
if (this.Class == DefaultClass.user)
yield return this;
else
{
foreach (ActiveDirectoryEntry ad in GetMembers())
{
foreach (ActiveDirectoryEntry m in ad.DeepEnumerateMembers())
{
yield return m;
}
}
}
}
public string Name
{
get
{
object o = this["cn"];
if
(o != null) return o.ToString();
o = this["ou"];
if
(o != null) return o.ToString();
return
Entry.Name;
}
}
public System.Security.Principal.SecurityIdentifier GetSID()
{
byte[] b = GetSIDArray();
if
(b == null) return null;
return
ActiveDirectory.GetSID(b);
}
byte[] GetSIDArray()
{
object o = GetValue("objectSid");
if
(o == null) return null;
byte
[] b = o as byte[];
if
(b == null)
{
if (o is Guid)
b = ((Guid)o).ToByteArray();
else
throw new Exception("Could not parse SID of type " + o.GetType().FullName);
}
return
b;
}
public int PrimaryGroupID
{
get { return GetValue<int>("primarygroupID"); }
}
public ActiveDirectoryEntry GetPrimaryGroup()
{
int pg = PrimaryGroupID;
if
(pg == 0) return null;
System.Security.Principal.SecurityIdentifier sid = GetSID();
if
(sid == null) return null;
string
s = sid.ToString();
int
i = s.LastIndexOf('-');
if
(i == -1) return null;
return
ActiveDirectory.GetBySID(s.Substring(0, ++i) + pg.ToString());
}
public T GetValue<T>(string Property)
{
object o = this[Property];
if
(o == null) return default(T);
return
(T)o;
}
public T GetValue<T>(string Property, T Default)
{
object o = this[Property];
if
(o == null) return Default;
return
(T)o;
}
public object GetValue(string Property)
{
return this[Property];
}

/// <summary>
///
When true, all changes are automatically comitted when a property is changed
///
</summary>
public
bool AutoSaveChanges = true;
public object this[string PropertyName]
{
get
{
return ActiveDirectoryProperty.GetValue(Properties[PropertyName]);
}
set

{
PropertyValueCollection pv = Properties[PropertyName];
if
(pv == null)
{
if (value == null) return;
throw
new Exception(PropertyName + " not found");
}
ActiveDirectoryProperty
.SetValue(pv, value);
if (AutoSaveChanges)
CommitChanges();
}
}

public PropertyCollection Properties
{
get { return Entry.Properties; }
}
/// <summary>
///
You can get the value directly by using the indexed property (e.g. YourEntry["PropertyName"] ), but
///
the <see cref="ActiveDirectoryProperty"/> encapsulates more functionality around the property.
///
</summary>
///
<param name="PropertyName"></param>
///
<returns></returns>
public
ActiveDirectoryProperty GetProperty(string PropertyName)
{
return new ActiveDirectoryProperty(this, PropertyName);
}
public IEnumerable<ActiveDirectoryProperty> GetProperties()
{
foreach (PropertyValueCollection pv in Properties)
{
yield return GetProperty(pv.PropertyName);
}
}
public DirectoryEntries Children
{
get { return Entry.Children; }
}
public override int GetHashCode()
{
return Path.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is ActiveDirectoryEntry &&
(obj as ActiveDirectoryEntry).Path == Path;
}
public override string ToString()
{
return Name;
}
public bool ImplementsClass(params string[] classes)
{
foreach (string s in Properties["objectClass"].Value as object[])
{
for (int i = 0; i < classes.Length; i++)
{
if (classes[i] == s) return true;
}
}
return
false;
}
}
public enum AccessType
{
None,
Direct,
Nested
}
public class ActiveDirectoryProperty
{
public readonly ActiveDirectoryEntry Entry;
public
readonly string PropertyName;
public
readonly bool IsSID;
PropertyValueCollection
pv;
public
ActiveDirectoryProperty(ActiveDirectoryEntry Entry, string PropertyName)
{
this.Entry = Entry;
this
.PropertyName = PropertyName;
this
.pv = Entry.Properties[PropertyName];
IsSID = PropertyName == "objectSid";
}

public bool AutoSaveChanges
{
get { return Entry.AutoSaveChanges; }
set
{ Entry.AutoSaveChanges = value; }
}
public object Value
{
get
{
return GetValue(pv);
}
set

{
Entry[PropertyName] = value;
}
}
public bool Exists
{
get
{
return pv != null;
}
}
public int Capacity
{
get
{
return pv.Capacity;
}
}
public bool HasValue
{
get { return pv != null && pv.Count > 0; }
}
public IEnumerable<object> GetValues()
{
if (!HasValue) yield break;
for
(int i = 0; i < pv.Count; i++)
{
yield return this[i];
}
}
public object this[int Index]
{
get
{
return ActiveDirectory.Convert(pv[Index]);
}
}
public string GetValueString()
{
return GetValueString(Environment.NewLine);
}
public string GetValueString(string Seperator)
{
if (!HasValue) return null;
string
res = null;
for
(int i = 0; i < pv.Count; i++)
{
if (i > 0) res += Seperator;
res += GetValueString(this[i]);
}
return
res;
}
string GetValueString(object o)
{
if (o is byte[])
{
if (IsSID) return ActiveDirectory.GetSID((byte[])o).ToString();
return
ActiveDirectory.ByteArrayToString((byte[])o);
}
return
o.ToString();
}
public override string ToString()
{
return PropertyName + ": " + GetValueString(", ");
}
public static object GetValue(PropertyValueCollection pv)
{
object o;
if
(pv == null || (o = pv.Value) == null) return null;
return
ActiveDirectory.Convert(o);
}
public static void SetValue(PropertyValueCollection pv, object value)
{
if (pv.Value == value) return;
if
(value == null)
pv.Remove(pv.Value);
else
{
if (pv.Value is IADsLargeInteger && value is DateTime)
{
//pv.Value = (int)(uint)((DateTime)value).ToFileTime();
ActiveDirectory
.SetDateTime(pv, (DateTime)value);
}
else
pv.Value = value;
}
}
}
/// <summary>
///
The default 'objectClass' types on which a search can be performed
///
</summary>
public
enum DefaultClass
{
any, group, user, organizationalUnit, computer, container
}
/// <summary>
///
Interface type to convert AD date time objects
///
</summary>
[ComImport]
[Guid("9068270B-0939-11D1-8BE1-00C04FD8D503")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
internal
interface IADsLargeInteger
{
[DispId(0x00000002)]
int
HighPart { get; set; }
[DispId(0x00000003)]
int
LowPart { get; set; }
}
/// <summary>
///
Static functions for querying Active Directory
///
</summary>
public
static class ActiveDirectory
{
#region Main get entries functions
public static IEnumerable<ActiveDirectoryEntry> GetEntries(string Filter)
{
return GetEntries(Filter, (ActiveDirectoryEntry)null);
}
public
static IEnumerable<ActiveDirectoryEntry> GetEntries(string Filter, string RootFilter)
{
ActiveDirectoryEntry root = null;
if
(RootFilter != null)
root = GetFirstEntry(RootFilter);
return GetEntries(Filter, root);
}
public
static IEnumerable<ActiveDirectoryEntry> GetEntries(string Filter, ActiveDirectoryEntry root)
{
return GetEntries(Filter, root, SearchScope.Subtree);
}
public
static IEnumerable<ActiveDirectoryEntry> GetEntries(string Filter, ActiveDirectoryEntry Root, SearchScope Scope)
{
AdSearchInfo si = new AdSearchInfo();
si.SearchRoot = Root;
si.Scope = Scope;
si.Filter = Filter;
return
si;
}
public static IEnumerable<ActiveDirectoryEntry> GetEntries(DefaultClass ObjectClass)
{
return GetEntries(new ADFilter(ObjectClass));
}
public static ActiveDirectoryEntry GetFirstEntry(string Filter)
{
return GetFirstEntry(Filter, null);
}
public
static ActiveDirectoryEntry GetFirstEntry(string Filter, ActiveDirectoryEntry root)
{
return GetFirstEntry(GetEntries(Filter, root));
}
public static ActiveDirectoryEntry GetFirstEntry(IEnumerable<ActiveDirectoryEntry> ie)
{
foreach (ActiveDirectoryEntry de in ie)
{
return de;
}
return
null;
}
#endregion
#region Specific entry searching/ supporting functions
public static ActiveDirectoryEntry GetFolder(params string[] Path)
{
ActiveDirectoryEntry de = null;
foreach
(string s in Path)
{
de = GetFolder(s, de);
if
(de == null)
return null;
}
return
de;
}
public
static ActiveDirectoryEntry GetFolder(string name, ActiveDirectoryEntry parent)
{
foreach (ActiveDirectoryEntry de in GetFolders(name, parent))
{
return de;
}
return
null;
}
public
static IEnumerable<ActiveDirectoryEntry> GetFolders(string name, ActiveDirectoryEntry parent)
{
if (string.IsNullOrEmpty(name)) name = "*";
return
GetEntries(string.Format( //"(|(&(cn={0})(objectClass=group))(ou={0}))"
"(ou={0})", name), parent, SearchScope.OneLevel);
}
public static IEnumerable<ActiveDirectoryEntry> GetContainers()
{
return GetContainers(null);
}
public static IEnumerable<ActiveDirectoryEntry> GetContainers(ActiveDirectoryEntry parent)
{
return GetEntries(new ADFilter(DefaultClass.container));
}
public static ActiveDirectoryEntry GetSecurityGroup(string group)
{
return GetFirstEntry(GetFilter(group, "group"));
}
public static ActiveDirectoryEntry GetBySID(System.Security.Principal.SecurityIdentifier sid)
{
return GetBySID(sid.Value);
}
public static ActiveDirectoryEntry GetBySID(byte[] sid)
{
StringBuilder sb = new StringBuilder();
for
(int i = 0; i < sid.Length; i++)
{
if (sid[i] < 33 || sid[i] > 125)
sb.Append("\\").Append(sid[i].ToString("x2"));
else
sb.Append((char)sid[i]);
}
return
GetBySID(sb.ToString());
}
public static ActiveDirectoryEntry GetBySID(string sid)
{
return GetFirstEntry(new ADFilter("objectSid", sid));
}
internal static System.Security.Principal.SecurityIdentifier GetSID(byte[] value)
{
return new System.Security.Principal.SecurityIdentifier(value, 0);
}
public static ActiveDirectoryEntry GetEntry(string cn)
{
return GetEntry(cn, null);
}

public static ActiveDirectoryEntry GetEntry(string cn, string objectclass)
{
return GetFirstEntry(GetFilter(cn, objectclass));
}
public static ActiveDirectoryEntry GetEntry(string cn, DefaultClass objectclass)
{
return GetEntry(cn, objectclass.ToString());
}
public static ADFilter GetFilter(string cn, string objectclass)
{
ADFilter filter = new ADFilter();
filter.objectClass = objectclass;
filter["cn"] = cn == null ? "*" : cn;
return
filter;
}
#endregion
#region Other AD related functions
public static int SynchronizeDomain(string TargetServer)
{
ad.DirectoryContext context = new ad.DirectoryContext(ad.DirectoryContextType.DirectoryServer, TargetServer);
int
res = 0;
using
(ad.DomainController dc = ad.DomainController.GetDomainController(context))
{
//dc.SyncReplicaFromAllServers(
foreach
(string partition in dc.Partitions)
{
dc.SyncReplicaFromAllServers(partition, ad.SyncFromAllServersOptions.AbortIfServerUnavailable);
res++;
}
}
return
res;
}
static IEnumerable<ad.DomainController> GetDomainControllers()
{
foreach (ad.DomainController dc in ad.Domain.GetCurrentDomain().DomainControllers)
{
yield return dc;
}
}
public static IEnumerable<string> GetDomainServers()
{
foreach (ad.DomainController dc in GetDomainControllers())
{
yield return dc.Name;
}
}
#endregion
#region DateTime support

public static bool IsDateTimeComObject(object o)
{
return o is IADsLargeInteger;
}
/// <summary>
///
Converts an <see cref="IADsLargeInteger"/> to a date time
///
</summary>
///
<param name="ComObject"></param>
///
<returns></returns>
public
static DateTime GetDateTime(object ComObject)
{
IADsLargeInteger li = (IADsLargeInteger)ComObject;
if
(li.LowPart == -1) return DateTime.MinValue;
long
date = (long)li.HighPart << 32 | (uint)li.LowPart;
try

{
return DateTime.FromFileTime(date);
}
catch
(Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
return
DateTime.MinValue;
}
}
const long lowmask = 0xFFFFFFFF;
internal
static void SetDateTime(PropertyValueCollection pv, DateTime value)
{
IADsLargeInteger li = (IADsLargeInteger)pv.Value;
long
l = value.ToFileTime();
li.HighPart = (int)(l >> 32);
li.LowPart = (int)(l & lowmask);
//Forces update

pv.Value = li;
}
/// <summary>
///
Formats the DateTime to LDIF
///
</summary>
///
<param name="dt"></param>
///
<returns></returns>
public
static string FormatDateTime(DateTime dt)
{
return dt.ToString(@"yyMMddHHmmssZ");
}
public static string FormatBoolean(bool b)
{
return b ? "TRUE" : "FALSE";
}
#endregion
#region Conversion
internal static object Convert(object o)
{
if (IsDateTimeComObject(o))
return GetDateTime(o);
if (o is byte[])
{
byte[] b = (byte[])o;
if
(b.Length == 16)
return new Guid(b);
}
//TODO: other com objects
return
o;
}
internal
static string ByteArrayToString(byte[] b)
{
StringBuilder sb = new StringBuilder("0x ");
for
(int i = 0; i < b.Length; i++)
{
sb.Append(b[i].ToString("X"));
}
return
sb.ToString();
}
#endregion


#if
Framework30
/// <summary>
/// Used for querying with Linq
/// </summary>
public static ADQuery Entries
{
get
{
return new ADQuery();
}
}
public static ADQuery<UserInfo> Users
{
get
{
return new ADQuery<UserInfo>(UserInfo.GetDefaultUserGroupsFilter());
}
}
#endif
}
#region Filter
public interface IADCriterium
{
}

public class ADFilter : List<IADCriterium>, IADCriterium
{
public ADFilter()
{
}
public
ADFilter(string Property, string Value)
{
this[Property] = Value;
}
public
ADFilter(DefaultClass ObjectClass)
{
this.ObjectClass = ObjectClass;
}
public ADLogicalOperator LogicalOperator;
public override string ToString()
{
if (Count == 0) return null;
if
(Count == 1) return this[0].ToString();
string
s = null;
foreach
(IADCriterium c in this)
{
s += c.ToString();
}
return
string.Format("({0}{1})", GetOperator(), s);
}
string GetOperator()
{
switch (LogicalOperator)
{
case ADLogicalOperator.AND:
return "&";
case ADLogicalOperator.OR:
return "|";
case ADLogicalOperator.NOT:
return "!";
default:
return null;
}
}
/// <summary>
///
Gets or sets the criterium for the secified property. If a property is set that was set
///
in a previous step, the previous property criterium will be overridden. For adding
///
multiple criteria of the same property, please use <see cref="Add"/>
///
</summary>
///
<param name="Property"></param>
///
<returns></returns>
public
object this[string Property]
{
get
{
ADCriterium c = GetCriterium(Property);
if
(c == null) return null;
return
c.Value;
}
set

{
ADCriterium c = GetCriterium(Property);
if
(c == null)
{
if (value != null) Add(Property, value);
}
else
if (value == null)
Remove(c);
else
c.Value = value;
}
}
public void Add(string Property, object value)
{
Add(new ADCriterium(Property, value));
}
public ADFilter AddSubFilter(ADLogicalOperator Operator)
{
ADFilter filter = new ADFilter();
filter.LogicalOperator = Operator;
Add(filter);
return
filter;
}
public
ADFilter AddSubFilter(ADLogicalOperator Operator,params IADCriterium[] criteria)
{
ADFilter filter = AddSubFilter(Operator);
filter.AddRange(criteria);
return
filter;
}
public ActiveDirectoryEntry GetFirstEntry()
{
return ActiveDirectory.GetFirstEntry(GetEntries());
}
public IEnumerable<ActiveDirectoryEntry> GetEntries()
{
return new AdSearchInfo(this);
}
public ADCriterium GetCriterium(string Property)
{
foreach (IADCriterium ic in this)
{
ADCriterium c = ic as ADCriterium;
if
(c != null && string.Equals(c.Property, Property, StringComparison.OrdinalIgnoreCase))
return c;
}
return
null;
}
[System.ComponentModel.DefaultValue(null)]
public
string objectClass
{
get
{
return this["objectClass"] as string;
}
set

{
this["objectClass"] = value;
}
}
[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
public
DefaultClass ObjectClass
{
get
{
string s = objectClass;
if
(s == null) return DefaultClass.any;
return
(DefaultClass)Enum.Parse(typeof(DefaultClass), s);
}
set

{
if (value == DefaultClass.any)
objectClass = null;
else
objectClass = value.ToString();
}
}
public static implicit operator string(ADFilter f)
{
if (f == null) return null;
return
f.ToString();
}
public
static implicit operator ADFilter(string s)
{
if (string.IsNullOrEmpty(s)) return null;
ADFilter
f = new ADFilter();
f.Add(new ADLiteralCriterium(s));
return
f;
}
public static implicit operator ADFilter(ADCriterium c)
{
ADFilter f = new ADFilter();
f.Add(c);
return
f;
}
public
static implicit operator ADFilter(DefaultClass cl)
{
ADFilter f = new ADFilter();
f.ObjectClass = cl;
return
f;
}
}
public class AdSearchInfo:IEnumerable<ActiveDirectoryEntry>
{
ADFilter filter;
public
ActiveDirectoryEntry SearchRoot;
public
List<string> PropertiesToLoad;
public
SearchScope Scope = SearchScope.Subtree;
public AdSearchInfo()
{
}
public
AdSearchInfo(ADFilter Filter)
{
this.filter = Filter;
}
public
AdSearchInfo(ADFilter Filter, IEnumerable<string> PropertiesToLoad):this(Filter)
{
this.PropertiesToLoad = new List<string>(PropertiesToLoad);
}
public ADFilter Filter
{
get
{
if (filter == null)
filter = new ADFilter();
return filter;
}
set

{
filter = value;
}
}
public void AddLoadProperty(string prop)
{
if (PropertiesToLoad == null)
PropertiesToLoad = new List<string>();
PropertiesToLoad.Add(prop);
}
#region IEnumerable<ActiveDirectoryEntry> Members
public IEnumerator<ActiveDirectoryEntry> GetEnumerator()
{
DirectoryEntry root = SearchRoot == null ? new DirectoryEntry() : SearchRoot.Entry;
using (DirectorySearcher ds = new DirectorySearcher(root, filter,
PropertiesToLoad==null ? null : PropertiesToLoad.ToArray()))
{
ds.SearchScope = Scope;
SearchResultCollection
res = ds.FindAll();
foreach
(SearchResult sr in res)
{
DirectoryEntry de = sr.GetDirectoryEntry();
if
(de.Name != root.Name)
yield return new ActiveDirectoryEntry(de);
}
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
public
enum ADLogicalOperator
{
AND, OR, NOT
}
[Flags]
public
enum Operator
{
Equals = 1,
Approxamately = 3,
GreaterThan = 4,
LessThan = 8
}
public class ADCriterium : IADCriterium
{
public readonly string Property;

///
<summary>
///
Operator, can be combined (eg combine ">" and "=" to get ">=" )
///
</summary>
public
Operator Operator;
public
object Value;
public
ADCriterium(string Property, object Value)
{
this.Property = Property;
this
.Value = Value;
}
public
ADCriterium(string Property, Operator Operator, object Value)
: this(Property, Value)
{
this.Operator = Operator;
}
protected virtual string GetFormattedValue()
{
if (Value == null) return null;
if
(Value is DateTime)
return ActiveDirectory.FormatDateTime((DateTime)Value);
if (Value is bool)
return ActiveDirectory.FormatBoolean((bool)Value);
return Value.ToString();
}
bool Implements(Operator o)
{
return (o & this.Operator) == o;
}
string
GetOperator()
{
if (Operator == 0) return "=";
string
res = null;
if
(Implements(Operator.Approxamately))
res += "~";
if (Implements(Operator.LessThan))
res += "<";
if (Implements(Operator.GreaterThan))
res += ">";
if (Implements(Operator.Equals))
res += "=";
return res;
}
public override string ToString()
{
return string.Format("({0}{1}{2})", Property, GetOperator(), GetFormattedValue());
}
}
public class ADLiteralCriterium : IADCriterium
{
public readonly string Criterium;
public
ADLiteralCriterium(string Criterium)
{
this.Criterium = Criterium;
}
public override string ToString()
{
return Criterium;
}
}
#endregion

#if
Framework30
public class ADQuery<T>
{
AdSearchInfo inf;
public ADQuery() :
this(null)
{
}
public ADQuery(ADFilter filter)
{
this.inf = new AdSearchInfo(filter);
}
public ADQuery<T> Where(Expression<Func<T,bool>> predicate)
{
Add(predicate,inf.Filter);
return this;
}
bool Add(Expression e,ADFilter filter)
{
if (e is LambdaExpression)
{
return Add(((LambdaExpression)e).Body, filter);
}
if (e is BinaryExpression)
{
var b= e as BinaryExpression;
ADLogicalOperator op = filter.LogicalOperator;
if (b.NodeType == ExpressionType.NotEqual)
{
filter = filter.AddSubFilter(ADLogicalOperator.NOT);
}
else if (b.NodeType == ExpressionType.AndAlso)
return Add(b, filter, ADLogicalOperator.AND);
else if(b.NodeType== ExpressionType.OrElse)
return Add(b, filter, ADLogicalOperator.OR);
return Add(b.Left,GetOperator(b.NodeType),b.Right,filter);
}
if (e is MethodCallExpression)
{
var m = e as MethodCallExpression;
var name = m.Method.Name;
if (m.Arguments.Count == 1)
{
if (name == "Equals")
{
return Add(m, Operator.Equals, m.Arguments[0], filter);
}
if (m.Arguments[0].Type == typeof(string))
{
string val =(string) (m.Arguments[0] as ConstantExpression).Value;
if (name == "StartsWith")
val += "*"
else if (name == "EndsWith")
val = "*" + val;
else if (name == "Contains")
val = "*" + val + "*"
else
val = null;
if(val!=null)
return Add(m, Operator.Equals, val, filter);
}
}
}
throw new NotSupportedException();
}
bool Add(Expression prop, Operator op, object value, ADFilter filter)
{
filter.Add(
new ADCriterium(
GetProperty(prop), op, GetValue(value)));
return true;
}
bool Add(BinaryExpression b, ADFilter filter, ADLogicalOperator op)
{
if (op != filter.LogicalOperator)
{
filter.Add(filter = new ADFilter());
filter.LogicalOperator = op;
}
return Add(b.Left, filter)
&& Add(b.Right, filter);
}
string GetProperty(Expression e)
{
if (e is MethodCallExpression)
{
}
if (e is LambdaExpression)
return GetProperty((e as LambdaExpression).Body);
if (e is MemberExpression)
{
var mi = (e as MemberExpression).Member;
foreach (ActiveDirectoryAliasAttribute alias in
mi.GetCustomAttributes(typeof(ActiveDirectoryAliasAttribute),true))
{
return alias.Alias;
}
return mi.Name;
}
if (e is MethodCallExpression)
{
return GetProperty((e as MethodCallExpression).Object);
}
throw new NotSupportedException();
}
object GetValue(object e)
{
if (e is Expression)
{
if (e is ConstantExpression)
return (e as ConstantExpression).Value;
throw new NotSupportedException();
}
return e;
}
Operator GetOperator(ExpressionType type)
{
if (type == ExpressionType.Equal || type== ExpressionType.NotEqual)
return Operator.Equals;
if (type == ExpressionType.LessThan)
return Operator.LessThan;
if (type == ExpressionType.LessThanOrEqual)
return Operator.LessThan | Operator.Equals;
if (type == ExpressionType.GreaterThan)
return Operator.GreaterThan;
if (type == ExpressionType.GreaterThanOrEqual)
return Operator.GreaterThan | Operator.Equals;
throw new NotSupportedException();
}

public IEnumerable<ActiveDirectoryEntry> Select(Expression<Func<T,ADQuery<T>>> selector)
{
return inf;
}

public IEnumerable<S> Select<S>(Expression<Func<T, S>> selector)
{
var ne = (selector as LambdaExpression).Body as NewExpression;
if (ne != null)
return Select<S>(ne);
return Select<S>(GetProperty(selector));
}
IEnumerable<S> Select<S>(string prop)
{
inf.AddLoadProperty(prop);
foreach (var entry in inf)
{
yield return entry.GetValue<S>(prop);
}
}
IEnumerable<S> Select<S>(NewExpression ne)
{
string[] props = new string[ne.Arguments.Count];
object[] pars = new object[props.Length];
int i = 0;
foreach (Expression e in ne.Arguments)
{
inf.AddLoadProperty( props[i++] = GetProperty(e));
}
foreach (var entry in inf)
{
for (i = 0; i < props.Length; i++)
{
pars[i] = entry[props[i]];
}
S res =(S) ne.Constructor.Invoke(pars);
yield return res;
}
}
}
public class ADQuery:ADQuery<ActiveDirectoryEntry>
{
public ADQuery() :
base()
{
}
public ADQuery(ADFilter filter):base(filter)
{
}
}
#endif
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public
class ActiveDirectoryAliasAttribute:Attribute
{
public readonly string Alias;
public
ActiveDirectoryAliasAttribute(string Alias)
{
this.Alias = Alias;
}
}
public partial class UserInfo
{
public UserInfo(string Name)
{
this.name = Name;
}
/// <summary>
///
The scope of this constructor is not made public because that would force projects
///
referencing the project with this code to have a reference to System.DirectoryServices.
///
Use GetInfo(ActiveDirectoryEntry) instead
///
</summary>
///
<param name="ActiveDirectoryEntry"></param>
protected
UserInfo(ActiveDirectoryEntry ActiveDirectoryEntry)
{
this.ActiveDirectoryEntry = ActiveDirectoryEntry;
}

#region AD
ActiveDirectoryEntry
adEntry;

public ActiveDirectoryEntry ActiveDirectoryEntry
{
get
{
if (!HasActiveDirectoryEntry)
throw new Exception(name + "  niet gevonden");
return adEntry;
}
set

{
if (adEntry == value) return;
adEntry = value;
name = null;
}
}
public bool HasActiveDirectoryEntry
{
get
{
if (adEntry == null && !string.IsNullOrEmpty(name))
adEntry = Search(name);
return adEntry != null;
}
}
public object this[string PropertyName]
{
get { return ActiveDirectoryEntry[PropertyName]; }
set

{
ActiveDirectoryEntry[PropertyName] = value;
}
}
public const string NameSearchField = "sAMAccountName";
protected virtual ActiveDirectoryEntry Search(string name)
{
ADFilter filter = new ADFilter();
filter.objectClass = "user";
filter[NameSearchField] = name;
return
ActiveDirectory.GetFirstEntry(filter);
}
string name;
public
virtual string Name
{
get
{
if (name == null)
name = this[NameSearchField].ToString();
return name;
}
}

public static UserInfo GetCurrentUser()
{
return new UserInfo(Environment.UserName);
}
public static UserInfo GetInfo(ActiveDirectoryEntry ActiveDirectoryEntry)
{
return new UserInfo(ActiveDirectoryEntry);
}
public static UserInfo GetInfo(string Name)
{
return new UserInfo(Name);
}
public static ADFilter GetDefaultUserGroupsFilter()
{
ADFilter filter = new ADFilter("userPrincipalName", "*"); //userPrincipalName = to prevent computers from being included
filter.ObjectClass = DefaultClass.user;
return
filter;
}
public static IEnumerable<UserInfo> GetUsers()
{
foreach (ActiveDirectoryEntry e in GetDefaultUserGroupsFilter().GetEntries())
yield return GetInfo(e);
}
#endregion
#region Account

public void SetPassword(string PW)
{
SetPassword(PW, false);
}
public
void SetPassword(string PW, bool MustChangeAtNextLogin)
{
// http://www.awprofessional.com/articles/article.asp?p=474649&seqNum=4&rl=1
ActiveDirectoryEntry.Invoke("SetPassword", new object[] { PW });
if (MustChangeAtNextLogin)
this["pwdLastSet"] = 0x0;
adEntry.CommitChanges();
adEntry.RefreshCache();
}
public bool AccountLocked
{
get
{
object o = this.ActiveDirectoryEntry.InvokeGet("IsAccountLocked");
if
(o == null) return false;
return
(bool)o;
}
}
const string prop_hidefromaddresslists = "msExchHideFromAddressLists";
public
bool HideFromAddressLists
{
get
{
return this.ActiveDirectoryEntry.GetValue<bool>(prop_hidefromaddresslists);
}
set

{
this.ActiveDirectoryEntry[prop_hidefromaddresslists] = value;
}
}
public static ADFilter GetHiddenFromAddressListFilter()
{
ADFilter filter = new ADFilter(DefaultClass.user);
filter[prop_hidefromaddresslists] = true;
return
filter;
}
public void UnlockAccount()
{
//unlock account
this
["LockOutTime"] = 0x0000;
}
public string GetStringValue(string property)
{
return ActiveDirectoryEntry[property] as string;
}
public DateTime GetDateTime(string Property)
{
return this.ActiveDirectoryEntry.GetValue<DateTime>(Property);
}
/// <summary>
///
Flags for the <see cref="ActiveDirectoryEntry"/> "userAccountControl" property flags
///
</summary>
//http://support.microsoft.com/default.aspx?scid=kb;en-us;305144

[Flags]
public
enum AccountFlags
{
Run_Logon_Script = 0x0001,
ACCOUNTDISABLE = 0x0002,
HomeDirectory_REQUIRED = 0x0008,
LOCKOUT = 0x0010,
Password_Not_Required = 0x0020,
///
<summary>
///
Note You cannot assign this permission by directly modifying the UserAccountControl attribute. For information about how to set the permission programmatically, see the "Property flag descriptions" section on http://support.microsoft.com/default.aspx?scid=kb;en-us;305144
///
</summary>
PASSWD_CANT_CHANGE = 0x0040,
ENCRYPTED_TEXT_PWD_ALLOWED = 0x0080,
TEMP_DUPLICATE_ACCOUNT = 0x0100,
NORMAL_ACCOUNT = 0x0200,
INTERDOMAIN_TRUST_ACCOUNT = 0x0800,
WORKSTATION_TRUST_ACCOUNT = 0x1000,
SERVER_TRUST_ACCOUNT = 0x2000,
DONT_EXPIRE_PASSWORD = 0x10000,
MNS_LOGON_ACCOUNT = 0x20000,
SMARTCARD_REQUIRED = 0x40000,
TRUSTED_FOR_DELEGATION = 0x80000,
NOT_DELEGATED = 0x100000,
USE_DES_KEY_ONLY = 0x200000,
DONT_REQ_PREAUTH = 0x400000,
PASSWORD_EXPIRED = 0x800000,
TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000,
}
public bool AccountEnabled
{
get
{
return (AccountStatus & AccountFlags.ACCOUNTDISABLE) == 0;
}
}
public AccountFlags AccountStatus
{
get
{
return (AccountFlags)this["userAccountControl"];
}
set

{
this["userAccountControl"] = value;
}
}
#endregion

#region Mail
[ActiveDirectoryAlias("mail")]
public
string Mail
{
get
{
return GetStringValue("mail");
}
}
const string altRecipient = "altRecipient";
[ActiveDirectoryAlias(altRecipient)]
public
bool IsMailForwarded
{
get
{
return this[altRecipient] != null;
}
}
protected virtual UserInfo CreateUserInstance(ActiveDirectoryEntry de)
{
return new UserInfo(de);
}
public UserInfo MailForwardedTo
{
get
{
string path = GetStringValue(altRecipient);
if
(path == null) return null;
path = ActiveDirectoryEntry.Prefix + path;
if
(!DirectoryEntry.Exists(path))
throw new Exception(path + " does not exist");
return CreateUserInstance(new ActiveDirectoryEntry(path));
}
set

{
this[altRecipient] = value == null ? null : value.ActiveDirectoryEntry.Path.Replace(value.ActiveDirectoryEntry.Prefix, null);
}
}
[ActiveDirectoryAlias("deliverAndRedirect")]
public
bool DeliverAndRedirect
{
get
{
object o = this["deliverAndRedirect"];
if
(o == null) return false;
return
(bool)o;
}
set

{
this["deliverAndRedirect"] = value;
}
}
#endregion

#region General properties
[ActiveDirectoryAlias("telephoneNumber")]
public
string Telephone
{
get
{
return GetStringValue("telephoneNumber");
}
}
[ActiveDirectoryAlias("msExchMailboxGuid")]
public
Guid ExchangeMailBoxGuid
{
get
{
object o = this["msExchMailboxGuid"];
return
new Guid((byte[])o);
}
}
[ActiveDirectoryAlias("sn")]
public
string SurName
{
get { return GetStringValue("sn"); }
}
[ActiveDirectoryAlias("givenName")]
public
string GivenName
{
get { return GetStringValue("givenName"); }
}
[ActiveDirectoryAlias("cn")]
public
string FullName
{
get { return GetStringValue("cn"); }
}
[ActiveDirectoryAlias("userPrincipalName")]
public
string PrincipalName
{
get { return GetStringValue("userPrincipalName"); }
}
[ActiveDirectoryAlias("profilePath")]
public
string ProfilePath
{
get { return GetStringValue("profilePath"); }
}
[ActiveDirectoryAlias("scriptPath")]
public
string LogonScript
{
get { return GetStringValue("scriptPath"); }
}
[ActiveDirectoryAlias("scriptPath")]
public
string ScriptPath
{
get { return GetStringValue("scriptPath"); }
}
[ActiveDirectoryAlias("lockoutTime")]
public
DateTime LockoutTime
{
get
{
return GetDateTime("lockoutTime");
}
}
[ActiveDirectoryAlias("pwdLastSet")]
public
DateTime PasswordLastSet
{
get
{
return GetDateTime("pwdLastSet");
}
}
[ActiveDirectoryAlias("displayName")]
public
string DescriptiveName
{
get
{
object o = this["displayName"];
if
(o == null)
o = this["cn"];
return o.ToString();
}
}
public string Container
{
get
{
return ActiveDirectoryEntry.Parent.GetValue<string>("ou");
}
}
public override string ToString()
{
return Name;
}
#endregion

#region Group membership
public bool IsMemberOf(string Group)
{
return ActiveDirectoryEntry.IsMemberOf(Group);
}
public bool IsMemberOf(ActiveDirectoryEntry group)
{
return ActiveDirectoryEntry.IsMemberOf(group);
}
public void MakeMemberOf(string Group)
{
if (IsMemberOf(Group)) return;
ActiveDirectoryEntry
de = ActiveDirectory.GetSecurityGroup(Group);
if
(de == null)
throw new Exception("Group not found");
de.Invoke("Add", this.ActiveDirectoryEntry.Entry.Path);
}
#endregion
}

}
. . .
posted @ 8:40 AM | Feedback (60)

Wednesday, April 04, 2007 #

Another one? Yep, I'm afraid it is just that: another date time picker control. I was using the default datetimepicker provided in the .net framework and liked a lot of its default features, but, like others, came upon some issues that made the dayly use of  it unacceptable for the target audience. 

The control itself is very simple, the .net 2.0 maskededitbox does most of the work, but the way the format is handled makes sure the input can take any part entered (eg only month and year, or just a day, or just a time). One of the things I liked about the standard control was the 'block' functionality to quickly switch between day,month, etc, so that feature had to added too. Of course the default calendar dropdown is also included.

While typing and filling a block (eg, the day), the next block (eg, the month) is selected automatically. Up and down arrows work as in the standard control: increasing or decreasing the selected block, but unlike the standard counterpart, the days are not forced to a value range (in this version, the range isn't forced at all...). Also didn't like to use a checkbox to indicate null as in the standard control, so null now is simply no input.

 

Code CopyHideScrollFull

namespace
DataLayer.Controls
{
using System;
using
System.Collections.Generic;
using
System.Text;
using
System.ComponentModel;
using
System.Windows.Forms;
using
System.Drawing;
using
System.Globalization;
[DefaultBindingProperty("BindingValue")]
[DefaultEvent("BindingValueChanged")]
[ToolboxBitmap(typeof(System.Windows.Forms.DateTimePicker))]    
public
partial class DateTimePicker:UserControl
{
public DateTimePicker()
{
InitializeComponent();
ResetFormat();
}

#region
Formatting

string
DefaultFormat;
public class MaskFormatInfo
{
public readonly string Format;
public
readonly string Mask;
public
readonly bool ContainsTime;
public
readonly bool ContainsDate;
public
MaskFormatInfo(string DateTimeFormat)
{
const string dtchars = "dMyHhms";
char[] fchars = new char[DateTimeFormat.Length * 2], mchars = new char[fchars.Length];
int
j = 0, index;
for
(int i = 0; i < DateTimeFormat.Length; i++)
{
char c = DateTimeFormat[i];
if
(c == '\\' && i < DateTimeFormat.Length - 1)
{
fchars[j] = mchars[j++] = c;
}
else
if ((index = dtchars.IndexOf(c)) != -1)
{
//ensure double entries to enable full user input
for
(int k = 0; k < 2; k++)
{
fchars[j] = c;
mchars[j++] = '0';
}
if
(i < DateTimeFormat.Length - 1 && DateTimeFormat[i + 1] == c) i++;
if
(index > 2)
ContainsTime = true;
else
ContainsDate = true;
continue;
}
fchars[j] = mchars[j++] = c;
}
this
.Format = new string(fchars, 0, j);
this
.Mask = new string(mchars, 0, j);    
}
}
private string format ;
public
string Format
{
get { return format; }
set

{
if (string.IsNullOrEmpty(value))
{
ResetFormat();
return
;
}
MaskFormatInfo
mf = new MaskFormatInfo(value);
format = mf.Format;
msk.Mask = mf.Mask;
pnlButton.Visible = showdropdown && mf.ContainsDate;
}
}
bool
ShouldSerializeFormat()
{
return !UsesDefaultFormat;
}
public
void ResetFormat()
{
DateTimeFormatInfo formatinfo = DateTimeFormatInfo.CurrentInfo;
string
format = null;
if (showdate)
format = formatinfo.ShortDatePattern;
if (showtime)
{
if (showdate) format += dtseperator;
format += formatinfo.ShortTimePattern;
}
Format = format;
DefaultFormat = this.format;
}
public bool UsesDefaultFormat
{
get
{
return format == DefaultFormat;
}
}
private string dtseperator=" ";
[DefaultValue(" ")]
[Description("This seperator used between date and time if both are shown. If a custom Format is set, this property is ignored")]
public
string SeperatorDateTime
{
get { return dtseperator; }
set

{
bool hasdefault = UsesDefaultFormat;
if
(string.IsNullOrEmpty(value))
dtseperator = " ";
else
dtseperator = value;
if (hasdefault) ResetFormat();
}
}
private bool showdate=true;
[DefaultValue(true)]
[Description("Gets or sets if the Date part is shown. If a custom Format is set, this property is ignored")]
public
bool ShowDate
{
get { return showdate; }
set

{
bool hasdefault = UsesDefaultFormat;
showdate = value;
if
(!value && !showtime) showtime = true;
if
(hasdefault) ResetFormat();
}
}

private bool showtime = true;
[DefaultValue(true)]
[Description("Gets or sets if the Time part is shown. If a custom Format is set, this property is ignored")]
public
bool ShowTime
{
get { return showtime; }
set

{
bool hasdefault = UsesDefaultFormat;
showtime = value;
if
(!value && !showdate) showdate= true;
if
(hasdefault) ResetFormat();
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
public
override string Text
{
get
{
return msk.Text;
}
set

{
msk.Text = value;
}
}
#endregion
#region Value

private DateTime? value;
[Browsable(false)]
[DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden)]
public
DateTime Value
{
get
{
if (value == null)
{
if (IsValid || DesignMode) //if IsValid: null allowed, return minvalue to prevent exceptions
return minvalue;
else
throw new Exception("No valid value entered");
}
return
value.Value;
}
set

{
this.value = value;
this
.msk.Text = value.ToString(format);
SetValid();
OnValueChanged();                
}
}
public
event EventHandler ValueChanged;
protected virtual void OnValueChanged()
{
OnBindingValueChanged();
if
(ValueChanged != null)
ValueChanged(this, EventArgs.Empty);
}
//[Browsable(false)]
[DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden)]
[TypeConverter(typeof(DateTimePicker.BindingValueConverter))]
[Editor("System.ComponentModel.Design.DateTimeEditor","System.Drawing.Design.UITypeEditor")] //,typeof(UITypeEditor))]
public
DateTime? BindingValue
{
get
{
return value;
}
set

{
if (this.value == value) return;
if
(value != null)
Value = value.Value;
else
{
this.value = null;
msk.Text = null;
SetValid();
OnBindingValueChanged();
}
}
}
bool
ShouldSerializeBindingValue()
{
return value != null;
}
public
void ResetBindingValue()
{
this.BindingValue = null;
}
public event EventHandler BindingValueChanged;
protected
virtual void OnBindingValueChanged()
{
if (BindingValueChanged != null)
BindingValueChanged(this, EventArgs.Empty);
}
bool allownull;
[DefaultValue(false)]
public
bool AllowNull
{
get
{
return allownull;
}
set

{
allownull = value;
SetValid();
}
}
[DefaultValue(false)]
public
bool IsNull
{
get { return value==null; }
}
#endregion
#region Designer Generated
private MaskedTextBox msk;
private
Panel pnlButton;

private void InitializeComponent()
{
this.msk = new System.Windows.Forms.MaskedTextBox();
this
.pnlButton = new System.Windows.Forms.Panel();
this
.SuspendLayout();
//

// msk

//

this
.msk.Dock = DockStyle.Fill;
this
.msk.BorderStyle = System.Windows.Forms.BorderStyle.None;
this
.msk.HidePromptOnLeave = true;
this
.msk.Location = new System.Drawing.Point(3, 2);
this
.msk.Mask = "00/00/0000   00:00";
this
.msk.Name = "msk";
this
.msk.PromptChar = ' ';
this
.msk.Size = new System.Drawing.Size(104, 13);
this
.msk.TabIndex = 0;
this
.msk.TextAlign = HorizontalAlignment.Center;
this
.msk.ValidatingType = typeof(System.DateTime);
this
.msk.Validating += new System.ComponentModel.CancelEventHandler(this.msk_Validating);
this
.msk.KeyUp += new System.Windows.Forms.KeyEventHandler(this.msk_KeyUp);
this
.msk.Click += new System.EventHandler(this.msk_Click);
//

// pnlButton

//

this
.pnlButton.BackColor = System.Drawing.SystemColors.Control;
this
.pnlButton.Dock = System.Windows.Forms.DockStyle.Right;
this
.pnlButton.Location = new System.Drawing.Point(98, 0);
this
.pnlButton.Name = "pnlButton";
this
.pnlButton.Size = new System.Drawing.Size(19, 18);
this
.pnlButton.TabIndex = 1;
this
.pnlButton.Click += new System.EventHandler(this.pnlButton_Click);
this
.pnlButton.Paint += new System.Windows.Forms.PaintEventHandler(this.pnlButton_Paint);
//

// DateTimePicker

//

this
.BackColor = System.Drawing.Color.White;
this
.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this
.Controls.Add(this.msk);
this
.Controls.Add(this.pnlButton);            
this
.Name = "DateTimePicker";
this
.Padding = new System.Windows.Forms.Padding(3, 0, 0, 0);
this
.Size = new System.Drawing.Size(117, 18);
this
.ResumeLayout(false);
this
.PerformLayout();
}
#endregion
#region Calendar dropdown
private void pnlButton_Paint(object sender, PaintEventArgs e)
{
ControlPaint.DrawComboButton(e.Graphics, pnlButton.ClientRectangle,
ButtonState.Normal);
}
private void pnlButton_Click(object sender, EventArgs e)
{
ShowCalendar();
}
void ShowCalendar()
{
new MontViewer(this).Show(this);
}
private bool showdropdown=true;
[DefaultValue(true)]
[Description("Gets or sets if the dropdown button is shown. If the format contains no date part, the button is always hidden")]
public
bool ShowDropDownButton
{
get { return showdropdown; }
set

{
if (showdropdown == value) return;
showdropdown = value;
Format = format;
}
}

/// <summary>
///
Drop down calendar form for the DateTimePicker
///
</summary>
class
MontViewer:Form
{
new DateTimePicker Owner;
MonthCalendar
mc = new MonthCalendar();
public
MontViewer(DateTimePicker Owner)
{
this.Owner = Owner;
this
.FormBorderStyle = FormBorderStyle.None;
this.ShowInTaskbar = false;
this
.KeyPreview = true;
this.Size = mc.Size;
Controls.Add(mc);
if
(!Owner.IsNull)
mc.SelectionStart = mc.SelectionEnd = Owner.Value;
mc.DateSelected += new DateRangeEventHandler(mc_DateSelected);
Rectangle r = Owner.RectangleToScreen(Owner.ClientRectangle);
this
.StartPosition = FormStartPosition.Manual;
this
.Location = new Point(r.Right - Width, r.Bottom);
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
Close();
else if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Space)
EnterDate();
else
base.OnKeyDown(e);
}
void
EnterDate()
{
DateTime res = mc.SelectionStart;
if
(!Owner.IsNull)
//make sure time part is not overwritten
res = res.Add(Owner.Value.TimeOfDay);
Owner.Value = res;
Close();
}
void mc_DateSelected(object sender, DateRangeEventArgs e)
{
EnterDate();
}
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
Close();
}
protected override void OnClosed(EventArgs e)
{
Owner.Focus();
base
.OnClosed(e);
}
}
#endregion
#region Validation
public bool IsValid
{
get
{
if (value != null)
return InBounds;
if (allownull && string.IsNullOrEmpty(text)) return true;
return
false;
}
}
bool InBounds
{
get
{
return value.Value >= minvalue && value.Value <= max;
}
}
void SetValid()
{
if (DesignMode) return;
Color
c;
if
(IsValid)
{
if (IsNull)
c = Color.AntiqueWhite;
else
c = Color.White;
lastException = null;
}
else

{
if (value != null && !InBounds)
c = Color.Orange;
else
c = Color.DarkOrange;
}
this
.BackColor = c;
}
Exception lastException;
string
text;

private void msk_Validating(object sender, CancelEventArgs e)
{
ValidateInput();
if
(blockinvalid && !IsValid)
e.Cancel = true;
}
void ValidateInput()
{
DateTime? value = null;
text = msk.Text;
if
(string.IsNullOrEmpty(text))
{
}
else

{                
try
{
string format = this.format;
if
(!msk.MaskFull)
{
char[] textchars = new char[format.Length],
formatchars = new char[textchars.Length];
int ci = 0;
for
(int i = 0; i < text.Length; i++)
{
if (char.IsDigit(text[i]))
{
textchars[ci] = text[i];
formatchars[ci++] = format[i];
}
}
if (ci > 0)
{
//check for single input chars
int
samecount = 1;
for
(int i = 1; i <= ci; i++)
{
if (formatchars[i] != formatchars[i - 1])
{
if (samecount == 1)
{
//insert extra
for
(int j = ci++; j >= i; j--)
{
formatchars[j] = formatchars[j - 1];
textchars[j] = textchars[j - 1];
}
textchars[i - 1] = '0';
i++;
}
samecount = 1;
}
else
samecount++;
}
text = new string(textchars, 0, ci);
format = new string(formatchars, 0, ci);
}
else
text = format = null;
}
if
(text != null)
{
value = DateTime.ParseExact(text, format, null);
if
(usecurrentmonth && format.IndexOf('M') == -1)
value = value.Value.AddMonths(DateTime.Today.Month - 1);
}
}
catch
(Exception ex)
{
lastException = ex;
value = null;
}
}
if
(value != null)
this.Value = value.Value;
else if (this.value != null)
{
this.value = null;
OnValueChanged();
}
SetValid();
}
private bool blockinvalid = false;
[DefaultValue(false)]
[Description("Normally invalid input is allowed but indicated as invalid. If this property is set to true, focus is held until the user enters a valid date")]
public
bool BlockInvalidInput
{
get { return blockinvalid; }
set
{ blockinvalid = value; }
}

static DateTime DefaultMin = new DateTime(1950, 1, 1);
private
DateTime minvalue = DefaultMin;
public
DateTime MinDate
{
get { return minvalue; }
set

{
minvalue = value;
SetValid();
}
}
bool
ShouldSerializeMinDate()
{
return minvalue != DefaultMin;
}
void
ResetMinDate()
{
MinDate= DefaultMin;
}
static DateTime DefaultMax = new DateTime(2500, 12, 31);
private
DateTime max = DefaultMax;
public
DateTime MaxDate
{
get { return max; }
set

{
max = value;
SetValid();
}
}
bool
ShouldSerializeMaxDate()
{
return max != DefaultMax;
}
void
ResetMaxDate()
{
MaxDate = DefaultMax;
}

#endregion
#region date block handling
void SelectBlock(int Offset)
{
int pos = msk.SelectionStart;
string
mask = msk.Mask;
int len = 1;
while
(true)
{
if (pos >= mask.Length)
pos = mask.Length - 1;
while (pos > 0 && mask[pos - 1] == '0')
--pos;
len = 1;
while
(len + pos < mask.Length && mask[len + pos] == '0')
len++;
if (Offset > 0)
{
pos += len + 1;
while
(pos < mask.Length && mask[pos] != '0')
pos++;
Offset--;
}
else
if (Offset < 0)
{
while (--pos >= 0 && mask[pos] != '0') { }
if
(pos <= 0)
{
pos = 0;
break
;
}
Offset++;
}
else
break;
}
msk.Select(pos, len);
}
void IncCurrent(int diff)
{
SelectBlock();
string
text = msk.SelectedText.Trim();
int cur = text.Length == 0 ? 0 : int.Parse(text);
cur += diff;
if
(cur < 0)
cur = 0;
int start = msk.SelectionStart, len = msk.SelectionLength;
msk.SelectedText = cur.ToString("d" + len);
msk.Select(start, len);
}
#endregion
#region ui events
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (HandleKey(keyData)) return true;
return
base.ProcessCmdKey(ref msg, keyData);
}
protected
override bool IsInputKey(Keys keyData)
{
if (keyData == Keys.Left || keyData == Keys.Right)
return true;
if (useupdown && (keyData == Keys.Up || keyData == Keys.Down))
return true;
return base.IsInputKey(keyData);
}
bool HandleKey(Keys k)
{
if (k == Keys.F3 || k == (Keys.Control | Keys.Down))
{
ShowCalendar();
}
else
if (useupdown && (k == Keys.Up || k == Keys.Down))
{
IncCurrent(k == Keys.Up ? 1 : -1);
}
else
if (k == Keys.Add)
{
IncCurrent(1);
}
else
if (k == Keys.Subtract)
{
IncCurrent(-1);
}
else
if (k == Keys.Left || k == Keys.Right)
{
SelectBlock(k == Keys.Right ? 1 : -1);
}
else
if (IsDigit(k) && msk.SelectionLength > 1)
{
clearblock();
return
false;
}
else
if (k == Keys.Delete || k==Keys.Back)
{
clearblock();
}
else
if (k == (Keys.T | Keys.Control))
SetToday();
else if (k == Keys.Enter)
{
Validate();
return
false;
}
else
if (k == Keys.End)
{
msk.SelectionStart = format.Length - 1;
SelectBlock();
}
else
if (k == Keys.Home)
{
msk.SelectionStart = 0;
SelectBlock();
}
else
return false;
return true;
}
void clearblock()
{
int i = msk.SelectionStart;
msk.SelectedText = new string(' ', msk.SelectionLength);
msk.SelectionStart = i;
}
public void SetToday()
{
Value = DateTime.Today;
msk.SelectionStart = 0;
SelectBlock();
}
private void msk_Click(object sender, EventArgs e)
{
SelectBlock();
}

private void msk_KeyUp(object sender, KeyEventArgs e)
{
if (msk.SelectionLength == 0 && IsDigit(e.KeyCode))
{
int i = msk.SelectionStart;
if
(i < msk.Mask.Length && msk.Mask[i] != '0')
{
SelectBlock(1);
}
}
}
bool IsDigit(Keys k)
{
if (k >= Keys.D0 && k <= Keys.D9)
return true;
return k >= Keys.NumPad0 && k <= Keys.NumPad9;
}
void SelectBlock()
{
SelectBlock(0);
}
private bool useupdown=true;
[DefaultValue(true)]
[Description("When this value is set to true, the up and down arrows are used to increment or decrement the selected value")]
public
bool HandleUpDown
{
get { return useupdown; }
set
{ useupdown = value; }
}
private bool usecurrentmonth = true;
[DefaultValue(true)]
[Description("Specifies that the current month should be used if no month was supplied.")]
public
bool UseCurrentMonth
{
get { return usecurrentmonth; }
set
{ usecurrentmonth = value; }
}


protected override void OnEnter(EventArgs e)
{
msk.Select();
msk.SelectionStart = 1;
SelectBlock();
base
.OnEnter(e);
}
public override Color BackColor
{
get
{
return base.BackColor;
}
set

{
base.BackColor = value;
msk.BackColor = value;
}
}
bool
ShouldSerializeBackColor()
{
return BackColor != Color.White;
}
public
new void ResetBackColor()
{
this.BackColor = Color.White;
}
#endregion
#region Designer
class BindingValueConverter : DateTimeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{                
if (value is string)
{
string s = value as string;
if
(string.IsNullOrEmpty(s)) return (DateTime?)null;                  
}
return
base.ConvertFrom(context, culture, value);
}
}
#endregion
}
}
. . .

PS. the main reasons the default didn't work for our scenario:

- The day's maximum was forced by the chosen month. Now for the default American notation where the month is entered before the day, this doesn't pose a problem. But since our regional settings fill the day first, the number entered was dependant on the month set in a previous step. For example: if the date was set to the fourth of april and the user starts typing a new date over it, the day cannot go beyond 30. Now to enter may 31st, the user would have to change the month field first and then go back to the day, which is not acceptable for quick data entry. Worse than that: the day would be set to 30 and the user might not even notice.

-The block did not switch to the next part when entering. When entering part of the date (eg. the day), the cursor would not jump to the next part (eg, the month) when filled. The user would have to press cursor keys first to switch. Again not acceptable for quick data entry. Now this is perhaps something that is solvable within the standard control, but didn't look into it that far, since the decision to create a new one had already been taken by that time ;-)

-No null binding property. This is of course a very simple one to overcome since it could be added to an inherited version, but to mention it none the less: a nullable field should be bindable directly to a property. If that field can be null, the property would have to be able to take null. Since .net 2.0 has that great new nullable syntax (DateTime?), that was easily enough done :D

-No Checkbox for null: this might be just personal preference, but for me null is nothing in the control and not changing a checkbox first. Besides that I had the feeling it would be confusing to use. Users might think that the date value was stored, but just not active or something of the kind.

-Take strange dates. Since the source can be unknown data, the control would have to be able take faulty text or strange input. The control would of course have to indicate that it is wrong, but the user shouldn't be stuck until he chooses. As long as he chooses before the data is stored to the database.


--------update 20-7-7---------
Some minor changes:
-Culture dependant formatting. The default format is now set to whatever the culture's default setting is.
-ShowTime and ShowDate properties. Using the default formatting, the date or time part can be hidden with these properties without fixing the format.
-Made the bindingvalue editable in designer instead of value, so the start value can be reset to null
posted @ 3:25 AM | Feedback (77)

Thursday, February 08, 2007 #

This class is a wrapper round an oledb connection to FoxPro. Don't know if there's much need for one, but I've had need for it on several occasions allready ;-)
FoxPro needs a set of specific tweaks to make it work with the default bindings and especially if you want to update, simply using the default data adapter won't work out of the box.
The class uses the default .net OleDB providers, but the machine the code is used on will need to have the FoxPro provider installed. The VFPOLEDB can be downloaded from http://www.microsoft.com/downloads/details.aspx?FamilyId=E1A87D8F-2D58-491F-A0FA-95A3289C5FD4&displaylang=en


The code of the connection class: Source Code

Usage is straight forward, create a connection. For example to connect to a folder:
FoxProConnection conn = new FoxProConnection(”c:\yourdatabasefolder”);

Of course instead of the folder, you can also use a dbc file. To get data out of an sql string, you can simply use the GetData commands. These will return a QueryResult object rather than the table directly. The advantage of this, is that you can use that object to update later on. If you don't need it, simply read out the Table property to get the table. Example:
conn.GetData(”select * from DbfName”);

To update, you have the choice to use the UpdateData method of the connection object, or to use the returned QueryResult object to update. An example of the latter:
QueryResult data = conn.GetData(”select * from table1”);
//you could also .net binding, for example: DataGridView1.DataSource =data;
//...some edits to the data.Table here
data.Update();

The above will work fine if the table has got a primary key set. In case it has not, you'll have to indicate which field(s) is the primary. This can also be doen with the queryresult object.
For example, using the data object from above and table1 having a field named “table1_key”, before the first update, set the keyfield:
data.SetKeyField(”table1_key”);

posted @ 2:43 PM | Feedback (73)

Thursday, October 26, 2006 #

In short: the continuous control is a control that in the designer can take any control (or multiple controls) and when bound to a bindingsource shows it as list of that entity of controls. Those having used access know the concept as a subform.


Click here for the source code: http://blogs.vbcity.com/hotdog/archive/2006/10/26/6567.aspx (last update 6/12/06)

For a Step by step quick example guide, click here
(The quick quickversion: add this control to a form, add a bindingsource, set the bindingsource property of the control to that bindingsource, drop some controls onto the continuous control (make sure they have databindings to the same source if they need to updated per record) and you're set to go.)

If, by any chance you want to know about how the control came to be, (or if you're simply have absolutely nothing interesting to do ;-)  ), visit this link: http://blogs.vbcity.com/hotdog/archive/2006/10/26/6571.aspx

Sample code will follow later


Screenshots:

Designer view with some controls dropped onto it

Simple runtime:

runtime with some options enabled:


Addition history
1/12/06:
* HeaderPanel option
* LeftToRight option
* fixed a small design time deviation when ShowSelectButtons was enabled
5/12/06:
* Insertbuttons (ability to show insert buttons between each item)
6/12/06:
* DragDrop (autodrag to always allow drag and AllowReorder to automatically drag items to other positions)

posted @ 8:48 AM

Wednesday, September 27, 2006 #

The title was what this class was mainly created for. It's a small wrapper that extends the System.IO.DriveInfo, mainly with the purpose of setting the drive mappings.

 

Code CopyHideScrollFull
namespace Subro.IO
{
using System;
using
System.Collections.Generic;
using
System.Text;
using
io = System.IO;
using
System.Runtime.InteropServices;
using
System.Diagnostics;
using
System.Drawing;
/// <summary>
///
Information on a drive. The 'default' information of <see cref="System.IO.DriveInfo"/>
///
can be obtained with the <see cref="Info"/> property.
///
Further added functionality includes: mappings to network locations, obtaining drive images
///

public
class DriveInfo
{
public readonly string Name;
io.DriveInfo info;
string
apidrive;
public
DriveInfo(string Drive)
{
this.Name = Drive;
apidrive = Drive.Trim();
if (!apidrive.EndsWith(":")) apidrive += ":";
Refresh();
}
#region Get info
public void Refresh()
{
unc = GetUNC();
try

{
info = new io.DriveInfo(Name);
}
catch
{ info = null; }
if
(info != null) type = info.DriveType;
}

string GetUNC()
{
StringBuilder remotename = new StringBuilder();
int
bufferlen = 255;
int
res = WNetGetConnection(apidrive, remotename, ref bufferlen);

if (CheckAPIRes(res) == API_Result.NO_ERROR)
{
return remotename.ToString();
}
return null;
}
API_Result CheckAPIRes(int res)
{
API_Result ares = (API_Result)res;
if
(res > 0)
Debug.WriteLine("Error: " + ares.ToString());
return ares;
}
#endregion
#region static
#region
API definitions
enum API_Result
{
ERROR_BAD_DEVICE = 1200,
ERROR_CONNECTION_UNAVAIL = 1201,
ERROR_EXTENDED_ERROR = 1208,
ERROR_MORE_DATA = 234,
ERROR_NOT_SUPPORTED = 50,
ERROR_NO_NET_OR_BAD_PATH = 1203,
ERROR_NO_NETWORK = 1222,
ERROR_NOT_CONNECTED = 2250,
NO_ERROR = 0
}


[DllImport("mpr.dll")]
static
extern int WNetGetConnection(string LocalName, StringBuilder RemoteName, ref int BufferSize);
[DllImport("shell32.dll")]
static
extern IntPtr ExtractIcon(int hInst, string lpszExeFileName, int nIconIndex);
//http://www.allapi.net/apilist/WNetAddConnection2.shtml
[DllImport("mpr.dll")]
static
extern int WNetAddConnection2A(ref NetResource ConnectionInfo, string pw, string user, ConnectionType Flags);
[StructLayout(LayoutKind.Sequential)]
struct
NetResource
{
/// <summary>ignored
public
int Scope;
public
ResourceType Type;
///
<summary>ignored
public
int DisplayType;
///
<summary>ignored
public
int Usage;
///
<summary>
///
Drive Name
///

public
string LocalName;
///
<summary>
///
The unc
///

public
string RemoteName;
///
<summary>ignored
public
string Comment;
///
<summary>
///
Provider
///

public
string Provider;
}
public enum ConnectionType
{
CurrentSessionOnly = 0,
///
<summary>
///
the change also counts when rebooted
///

Update_Profile = 1
}
public enum ResourceType
{
Disk = 1,
Printer = 2
}

//http://www.allapi.net/apilist/WNetCancelConnection2.shtml
[DllImport("mpr.dll")]
static
extern int WNetCancelConnection2A(string Drive, ConnectionType Flags, int Force);
#endregion
public static IEnumerable<DriveInfo> GetDrives()
{
return GetDrives(false);
}
public
static IEnumerable<DriveInfo> GetMappings()
{
foreach (DriveInfo inf in GetDrives(true))
{
if (inf.HasMapping) yield return inf;
}
}
static IEnumerable<DriveInfo> GetDrives(bool IncludeMappings)
{
foreach (string s in GetDriveNames(IncludeMappings))
{
DriveInfo inf = new DriveInfo(s);
if
(inf.Exists || (IncludeMappings && inf.HasMapping)) yield return inf;
}
}
/// <summary>
///
Returns an enumeration of all POSSIBLE drive/mapping names
///

///
<param name="IncludeMappings">
///
<returns>
public
static IEnumerable<string> GetDriveNames(bool IncludeMappings)
{
for (char c = 'A'; c <= 'Z'; c++)
{
yield return c.ToString();
}
if
(IncludeMappings)
for (int i = 1; i < 4; i++)
{
yield return "LPT" + i;
}
}
#endregion
#region props
#region mapping
public bool HasMapping
{
get { return unc != null; }
}
string unc;
public
string Mapping
{
get
{
return unc;
}
set

{
if (unc == value) return;
if
(value == null)
{
RemoveMapping();
}
else

{
SetMapping(value, ConnectionType.Update_Profile);
}
Refresh();
}
}
public void RemoveMapping()
{
RemoveMapping(ConnectionType.Update_Profile);
}
public
void RemoveMapping(ConnectionType Persistance)
{
int res = WNetCancelConnection2A(apidrive, Persistance, 1);
CheckAPIRes(res);
Refresh();
}
public void SetMapping(string UNC, ConnectionType ConnectionType)
{
SetMapping(UNC, ConnectionType, ResourceType.Disk);
}
public
void SetMapping(string UNC, ConnectionType ConnectionType, ResourceType ResourceType)
{
if (UNC == null)
throw new ArgumentNullException("UNC");
if (unc != null && unc.ToLower() == UNC.Trim().ToLower()) return;
RemoveMapping(ConnectionType);
NetResource
nr = new NetResource();
nr.LocalName = apidrive;
nr.RemoteName = UNC;
nr.Type = ResourceType;
int
res = WNetAddConnection2A(ref nr, null, null, ConnectionType);
CheckAPIRes(res);
Refresh();
}
#endregion
/// <summary>
///
Indicates if the drive is known on the current system
///

public
bool Exists
{
get { return info != null && info.DriveType != System.IO.DriveType.NoRootDirectory; }
}
private io.DriveType type;
public io.DriveType DriveType
{
get { return type; }
}
public override string ToString()
{
string res = apidrive;
if
(!Exists && !HasMapping)
{
res += " [Does not exist]";
}
else

{
if(Exists)
res += " [" + type + " drive]";
if (HasMapping)
res += " --> " + unc;

}
return
res;
}
#endregion
#region Graphical
static Icon GetIcon(int index)
{
IntPtr handle = ExtractIcon(0, "shell32.dll", index);
return
Icon.FromHandle(handle);
}
static
int GetIndex(io.DriveType type)
{
switch (type)
{
case System.IO.DriveType.CDRom:
return 11;
case System.IO.DriveType.Fixed:
return 8;
case System.IO.DriveType.Network:
return 9;
case System.IO.DriveType.Ram:
return 12;
case System.IO.DriveType.Removable:
return 6;
case System.IO.DriveType.Unknown:
return 7;
}
return
-1;
}
/// <summary>
///
Gets the icon normally associated with the drive
///

///
<returns>
public
Icon GetIcon()
{
int index = GetIndex(type);
if
(index == -1) return null;
return
GetIcon(index);
}
public Image GetImage()
{
Icon ic = GetIcon();
if
(ic == null) return null;
Bitmap
res = Bitmap.FromHicon(ic.Handle);
return
res;
}
#endregion
}

}
. . .
posted @ 3:30 AM

Friday, July 28, 2006 #

For starters, I should mention I'm not what you'd call an experienced PowerPoint user. But like most, I use it now and then. The things you can do with it are great too, but I just couldn't find an easy way to relink url's of movie objects. Relative didn't seem to be possible unless the movies were in the same folder as the presentation itself.

Googling seems to indicate that PP always links to absolute paths, with no option to turn to relative and besides that: couldn't even find a way to change the url in PP itself. The only way seemed to be to add a new object when the presentation was moved. Not the thing that was wanted :(

Luckily VBA can do a lot more than the default stuff and so, for anyone interested, the following code was created. It relinks all movie objects (a lot easier than doing it one by one manually anyway)
Off course, if I missed some easy option inside PP, do tell. :) (as I may have mentioned somewhere in this overly long intro, I haven't used PP that much :p )

Install
For those not familiar with VBA, but do want to use this functionality:
-open the VBA editor (Tools->Macro->Visual basic editor)
-Add a new module (Insert->Module)
-Paste the code below inside that module
After this, you can simply run the macro's from within PP itself. Simply go to Tools->Macro->Macros and you can choose either RelinkMoviesToDefaultLocation or RelinkMoviesAndAskForLocation to run (The difference is explained below)

----------------------------------------------------

Option Explicit

Public Const DefaultMovieSubFolder  As String = "Movies"

'relinks all movies to the default subdirectory beneath the presentations location
'the name of the subdirectory is set in the DefaultMovieSubFolder constant
Public Sub RelinkMoviesToDefaultLocation()
    Dim folder As String
    folder = GetDir(ActivePresentation.FullName) & DefaultMovieSubFolder
    RelinkMovies folder
End Sub

'Ask the user for a directory and relinks all movie objects to that location
Public Sub RelinkMoviesAndAskForLocation()
    Dim folder As String
    folder = InputBox("Please enter target directory", , GetDir(ActivePresentation.FullName))
    If Len(folder) = 0 Then Exit Sub
    RelinkMovies folder
End Sub

Public Sub RelinkMovies(Target As String)
    If Len(Dir(Target, vbDirectory)) = 0 Then
        MsgBox "The target directory (" + Target + ") does not exist. Relinking cancelled"
        Exit Sub
    End If
   
    Dim sl As Slide, sh As Shape, count As Integer, relinked As Integer
    If Not Right$(Target, 1) = "\" Then Target = Target & "\"
    For Each sl In ActivePresentation.Slides
        For Each sh In sl.Shapes
            If IsMovie(sh) Then
                If Relink(sh, Target) Then relinked = relinked + 1
                count = count + 1
            End If
        Next
    Next
    MsgBox "Finished: " & count & " movie objects were checked and " & relinked & " were relinked"
End Sub

Private Function IsMovie(sh As Shape)
    On Error Resume Next
    IsMovie = sh.MediaType = ppMediaTypeMovie
End Function

Function Relink(sh As Shape, TargetDir As String) As Boolean
    Dim File As String, Current As String
    File = sh.LinkFormat.SourceFullName
    Current = GetDir(File)
   
    If Current = TargetDir Then
        Exit Function 'no change made
    End If
   
    File = TargetDir & Mid$(File, Len(Current) + 1)
    sh.LinkFormat.SourceFullName = File
    Relink = True
End Function


Private Function GetDir(File As String) As String
    Dim i As Integer
    i = InStrRev(File, "\")
    If i = 0 Then
        GetDir = ""
    Else
        GetDir = Left$(File, i)
    End If
End Function

 

posted @ 3:01 AM

Monday, July 10, 2006 #

Another small class with nothing fancy or advanced, but that could spare time in not having to do it yourself ;-)

.net has a lot of nifty debugging features. For Runtime support a lot of  convenient code exists in the System.Diagnostics namespace.
But.. if you're anything like me, you use the Console.Write and WriteLine functions when developing. (when sometimes I really should use the Debug.Write... functions, but find it too convenient to use Console)

Or.. other times when written for a console app, but now you want the same code to run in a windows app and still see the output.

In any case, .net has made  it incredibly easy to redirect the standard output. No tutorial here on that subject, but still, had made a default output several times and decided that I could do with something generic.
Well, here it is. It's very simple code which does nothing more than redirect the Console output to a listbox. It runs asynchronously with the calling code, so it won't hold it up that much, but the code might be ahead of the output in fast output generating output files.

To start using, simply set:
Subro.ConsoleOutput.Redirect = true;

To stop, surprise surprise, set Redirect to false. By default the maximum number of lines kept (and displayed) is 250, but this can be changed by setting the ConsoleOutput.MaxLines property

Code CopyHideScrollFull
namespace Subro
{
using System.Threading;
using
System.IO;
using
System;
using
System.Windows.Forms;
using
System.Drawing;
using
System.Text;
using
System.ComponentModel;
public
class ConsoleOutput : Form
{
static volatile ConsoleOutput instance;
///
<summary>
///
Starts or stops the redirection of the console output to a ConsoleOutput form window
///

public
static bool Redirect
{
get
{
return instance != null;
}
set

{
if (Redirect == value) return;
WaitLock();
//instanceLock.AcquireReaderLock(timeout);

if
(Redirect != value)
{
locked = true;
if
(value)
{
Thread t = new Thread(new ThreadStart(show));
t.Start();
}
else

{
instance.Invoke(new ThreadStart(instance.Close));
}
}
WaitLock();
}
}
const
int timeout = 5000;
volatile
static bool locked;
//static ReaderWriterLock instanceLock = new ReaderWriterLock();

///
<summary>
///
Thread start to start showing the output form in its own thread
///

static
void show()
{
new ConsoleOutput().ShowDialog();
}
static
void WaitLock()
{
while (locked) { }
}
static
volatile int max = 250;
public
static int MaxLines
{
get { return max; }
set

{
max = value;
if
(instance != null)
instance.Invoke(new ThreadStart(instance.CheckMax));
}
}

static
bool ShowTime = true;
public
static bool ShowEntryTimes
{
get { return ShowTime; }
set

{
ShowTime = value;
if
(instance != null)
instance.Invalidate();
}
}
/// <summary>
///
The original output of the console window
///

TextWriter
orgoutput;
protected
override void OnClosing(CancelEventArgs e)
{
if (orgoutput != null)
Console.SetOut(orgoutput);
if (stream != null)
stream.Dispose();
instance = null;
locked = false;
base
.OnClosing(e);
}
protected
override void OnShown(EventArgs e)
{
base.OnShown(e);
instance = this;
stream = new CStream(this);
orgoutput = Console.Out;
Console
.SetOut(stream);
locked = false;
}
protected
override Size DefaultSize
{
get
{
return new Size(500, 300);
}
}
protected
override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape) Close();
base
.OnKeyDown(e);
}
CStream
stream;
ConsoleBox
lb = new ConsoleBox();
ConsoleOutput()
{
lb.Dock = DockStyle.Fill;
Controls.Add(lb);
Text = "Console output";
KeyPreview = true;
}
class ConsoleBox : ListBox
{
public ConsoleBox()
{
BackColor = Color.Black;
ForeColor = Color.White;
brush = new SolidBrush(ForeColor);
DrawMode = DrawMode.OwnerDrawVariable;
}
SolidBrush
brush;
static
SolidBrush timeBrush = new SolidBrush(Color.Yellow);
static
Font timefont = new Font(FontFamily.GenericSansSerif, 7);
static
int timeHeight;
protected override void OnDrawItem(DrawItemEventArgs e)
{

if
(e.Index == -1) return;
Graphics
g = e.Graphics;
e.DrawBackground();
ConsoleEntry
ce = Items[e.Index] as ConsoleEntry;
Rectangle
rect = e.Bounds;
if
(ShowTime)
{
g.DrawString(ce.GetCreationTime(), timefont, timeBrush, rect);
rect.Offset(0, timeHeight);
}
g.DrawString(ce.Value, e.Font, brush,rect);
}
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
ConsoleEntry ce = Items[e.Index] as ConsoleEntry;
SizeF
size= e.Graphics.MeasureString(ce.Value, Font);
e.ItemHeight = (int)size.Height;
if
(ShowTime)
{
if (timeHeight == 0)
timeHeight = (int)
e.Graphics.MeasureString(ce.GetCreationTime(), timefont).Height;
e.ItemHeight += timeHeight;
}
}
}

void
CheckMax()
{
stream.CheckMax();
}
class
ConsoleEntry : INotifyPropertyChanged
{
public readonly DateTime CreationTime = DateTime.Now;
public
string Value
{
get { return value; }
set

{
this.value = value;
if
(ValueChanged != null)
ValueChanged(this, EventArgs.Empty);
if (PropertyChanged != null)
PropertyChanged(this, pe);
}
}
public
event EventHandler ValueChanged;
public
event PropertyChangedEventHandler PropertyChanged;
static
PropertyChangedEventArgs pe = new PropertyChangedEventArgs("Value");
string
value;
public
ConsoleEntry(string Value)
{
this.value = Value;
}
public
override string ToString()
{
return value;
}
public string GetCreationTime()
{
return CreationTime.ToString("hh:mm:ss.ffff");
}
}
class
CStream : TextWriter
{
ConsoleOutput owner;
ListBox
lb;
public
CStream(ConsoleOutput owner)
{
this.owner = owner;
lb = owner.lb;
lb.DataSource = list;
lb.DisplayMember = "Value";
}
public
override void Write(string value)
{
Write(value, false);
base
.Write(value);
}
public
override void WriteLine(string value)
{
Write(value, true);
base
.WriteLine(value);
}
ConsoleEntry
last;
BindingList
<ConsoleEntry> list = new BindingList<ConsoleEntry>();
delegate
void addDelegate(string value, bool end);
void
Write(string value, bool end)
{
if (locked) return;
try

{
lb.BeginInvoke(new addDelegate(write), new object[] { value, end });
}
catch
(ObjectDisposedException)
{
}
catch
(InvalidOperationException)
{
lb.CreateControl();
Write(value, end);
}
}
int
index;
void
write(string value, bool end)
{
lock (list)
{
if (last == null)
{
last = new ConsoleEntry(value);
list.Add(last);
index = list.Count - 1;
}
else

{
last.Value += value;
}
lb.SelectedIndex = index;
CheckMax();
if
(end)
last = null;
}
}
public
void CheckMax()
{
if (list.Count <= max) return;
lock
(list)
while (list.Count > max)
{
list.RemoveAt(0);
index--;
}
//list.RemoveRange(0, list.Count - max);
}
public
override Encoding Encoding
{
get { return Encoding.Unicode; }
}
}
}
}
. . .

 

26-6-6
Understandably, comments have arisen and might still arise about the very ugly thread locking procedure. Well, let's just keep it at that it got it reasons and that it is hardly used when running. Please see the comments below for further explanation :
http://blogs.vbcity.com/hotdog/archive/2006/07/10/6089.aspx#6120

But... please feel free to experiment for other small impact solutions, but when testing elaborately I hope you agree that the normal locking mechanisms don't do the job in this case. To be honest, I didn't really look any further because the lock is needed only when starting (or stopping) the redirect, which takes a few milliseconds and will not be noticed. So experimenting on a large scale could ultimately be a waste of your time :p ;-)

posted @ 6:31 AM

Thursday, June 01, 2006 #

For the description of most controls we use labels and I for one don't like to add a label manually for each control. Been using a component that paints values, but never got around to put it in a nice coat and with design time support up until now. The design time being the most work (although greatly alleviated by finding out how to use the isComplete parameter of the InstanceDescriptor ;-) ), the component is easy in use and deployment.
Usage: drop it to your form (or container control) and you get a new property on each control (”Label on LabelProviderName”).
You can simply type in the labeltext and a label will be painted on its parent.

To set more details, when the text is set a LabelInfo object will be created automatically. A plus sign appears in front of the property once it has been set, to indicate it can be expanded. Other properties for the label such as the Alignment, Font and Color can then be set.
To make things complete, the LabelProvider component itself also contains a set of those properties. This is the leading set. Unless a property is set specifically for a single label, the settings of the main component will be used for all labels. (The effects can be easily tried out in the designer)


Per request some quick setup instructions:
Usage
Add this code to your project (or a dll containing this code). The component will automatically show up in the visual studio 2005 toolbox. Drag the LabelProvider to your form or control in the form designer.
Now each control you select will have an additional property: ”Label on ...”, where ... is the name of the component. The text you type in in this property, will become the label for that control.

That's it for the normal label usage. For more settings, you can change the settings on the component itself. Or you can change the settings per label: press the [+] in front of the “Label on ...“  property for additional settings

Added per 22-9-6:
ability to show icon (or show only icon, then a tooltip will be shown for the text, thus the labelprovider becomes an infoprovider)
Changable cursor
Click event
Blinking possibility

Small thing added on 19-10-6:
text is drawn as disabled if the attached control is disabled

 

Code CopyHideScrollFull
using System;
using
System.Collections.Generic;
using
System.Text;
using
System.ComponentModel;
using
System.ComponentModel.Design;
using
System.ComponentModel.Design.Serialization;
using
System.Drawing;
using
System.Drawing.Design;
using
System.Windows.Forms;
using
System.Windows.Forms.Design;
using
System.Reflection;

namespace
Subro.Controls
{
[ProvideProperty("Label", typeof(Control))]
[ToolboxBitmap(typeof(Label))]
[DefaultEvent("LabelClicked")]
public
class LabelProvider : Component, IExtenderProvider
{
#region Constructors
public
LabelProvider()
: this(null)
{
}
public
LabelProvider(IContainer container)
{
if (container != null)
container.Add(this);
defsettings.SettingChanged += new EventHandler(defsettings_SettingChanged);
}
#endregion
#region
IExtenderProvider Members
public
bool CanExtend(object extendee)
{
return extendee is Control;
}

int IndexOf(Control c)
{
for (int i = 0; i < list.Count; i++)
{
if (list[i].Control == c) return i;
}
return
-1;
}
[DefaultValue(null)]
public
LabelInfo GetLabel(Control c)
{
int i = IndexOf(c);
if
(i == -1)
{
return null;
}
return
list[i].LabelInfo;
}
public void SetLabel(Control c, LabelInfo value)
{
int i = IndexOf(c);
if
(value == null)
{
if (i == -1) return;
list[i].Dispose();
list.RemoveAt(i);
}
else
if (i == -1)
{
list.Add(new ControlLabel(this, c, value));
}
else

{
list[i].LabelInfo = value;
}
}
public class LabelClickedEventArgs : EventArgs
{
public readonly Control Control;
public
readonly LabelInfo LabelInfo;
public
LabelClickedEventArgs(Control c, LabelInfo inf)
{
this.Control = c;
this
.LabelInfo = inf;
}
}
public event EventHandler<LabelClickedEventArgs> LabelClicked;
void OnLabelClicked(ControlLabel l)
{
if (LabelClicked != null)
LabelClicked(this, new LabelClickedEventArgs(l.Control, l.LabelInfo));
}

class ControlLabel : IDisposable
{
public readonly Control Control;

LabelProvider Owner;
public
ControlLabel(LabelProvider Owner, Control c, LabelInfo li)
{
if (c == null) throw new ArgumentNullException();
this
.Control = c;
this
.LabelInfo = li;
this
.Owner = Owner;
li.DefaultSettings = Owner.defsettings;
ResetParent();
Control.ParentChanged += ehParentChanged;
Control.LocationChanged += ehLocationChanged;
Control.EnabledChanged += new EventHandler(Control_EnabledChanged);
}

#region enter/leave
bool focused, cursorchanged;
public
bool Focused
{
get { return focused; }
}
Control ParentControl
{
get { return Parent.Parent; }
}
Cursor
beforeEnter;
public
void CheckEnterLeave(Point p)
{
CheckEnterLeave(bounds.Contains(p));
}
void
CheckEnterLeave(bool hasfocus)
{
if (hasfocus != focused)
{
if (hasfocus)
{
if (inf.Cursor != null && inf.Cursor != ParentControl.Cursor)
{
beforeEnter = ParentControl.Cursor;
ParentControl.Cursor = inf.Cursor;
cursorchanged = true;
}
if
(!inf.ShowText)
Parent.ShowTooltip(this, new Point((int)bounds.Right,(int)bounds.Top));
}
else

{
if (cursorchanged)
{
ParentControl.Cursor = beforeEnter;
cursorchanged = false;
}
Parent.RemoveTooltip(this);
}
focused = hasfocus;
}
}
public void Click()
{
Owner.OnLabelClicked(this);
}
#endregion
#region
label
LabelInfo
inf;
public
LabelInfo LabelInfo
{
get
{
return inf;
}
set

{
RemoveInfoEvents();
inf = value;
if
(inf != null)
inf.SettingChanged += ehSettingChanged;
Invalidate(true);
}
}
void
LabelInfo_SettingChanged(object sender, EventArgs e)
{
Invalidate(true);
}
void
Control_EnabledChanged(object sender, EventArgs e)
{
Invalidate(false);
}
EventHandler
ehSettingChanged
{
get
{
return new EventHandler(LabelInfo_SettingChanged);
}
}
void
RemoveInfoEvents()
{
if (inf != null)
{
inf.SettingChanged -= ehSettingChanged;
}
}
RectangleF bounds;
void
Invalidate(bool ClearBounds)
{
if (ClearBounds)
{
if (!bounds.IsEmpty) bounds = new RectangleF();
StopBlink();
}
if
(Parent != null)
Parent.Invalidate(this);
}
public bool BoundsSet
{
get { return !bounds.IsEmpty; }
}
public RectangleF Bounds
{
get { return bounds; }
}
#endregion

#region label bounds
public
bool ContainsLabel
{
get
{
return inf != null &&
(!string.IsNullOrEmpty(inf.Text) || inf.ShowIcon);
}
}
public
void Paint(Graphics g)
{
if (!ContainsLabel) return;
if
(bounds.IsEmpty) InitBounds(g);
if
(blinker != null && blinker.ShouldHide) return;
inf.Paint(g, bounds,this.Control.Enabled);
}
void
InitBounds(Graphics g)
{
bounds = inf.GetBounds(g, Control.Bounds);
InitBlink(inf.Blink);
}
private void InitBlink(LabelInfo.BlinkInfo blinkInfo)
{
if (blinkInfo == null || blinkInfo.Style == BlinkStyle.None || blinkInfo.Rate == 0) return;
blinker = new Blinker(blinkInfo, this);
}
void StopBlink()
{
if (blinker != null)
{
blinker.Stop();
blinker = null;
}
}
Blinker
blinker;
class
Blinker
{
bool hide;
LabelInfo
.BlinkInfo inf;
ControlLabel Owner;
DateTime
start = DateTime.Now;
bool
hasend;
DateTime
end;
public
Blinker(LabelInfo.BlinkInfo inf, ControlLabel Owner)
{
this.inf = inf;
this
.Owner = Owner;
hasend = inf.Style == BlinkStyle.LimitedTime && inf.Duration > 0;
if
(hasend) end = start.AddMilliseconds(inf.Duration);
SetTimer();
}
System.Threading.Timer timer;
public bool ShouldHide
{
get { return hide; }
}
void Invalidate()
{
if (Owner.Control.InvokeRequired)
Owner.Control.Invoke(new System.Threading.ThreadStart(Invalidate));
else
Owner.Invalidate(false);
}
void
SwitchHide(object state)
{
if (hasend && DateTime.Now >= end)
Stop();
else if (!stopped)
{
hide = !hide;
Invalidate();
}
}
void SetTimer()
{
timer =
new System.Threading.Timer(
  new System.Threading.TimerCallback(SwitchHide),
null, inf.Rate, inf.Rate);
}

bool stopped;
public
void Stop()
{
stopped = true;
if
(hide)
{
hide = false;
Invalidate();
}
//timer.Dispose();

//timer = null;
}
}
#endregion
#region
location changed

void Control_LocationChanged(object sender, EventArgs e)
{
Invalidate(true);
}
EventHandler
ehLocationChanged
{
get { return new EventHandler(Control_LocationChanged); }
}
#endregion
#region
Parent
ParentReference
Parent;
void
RemoveFromParent()
{
if (Parent != null)
{
Parent.Remove(this);
}
}
void
ResetParent()
{
RemoveFromParent();
Parent = Owner.GetParentReference(this);
}
void
Control_ParentChanged(object sender, EventArgs e)
{
ResetParent();
}
EventHandler
ehParentChanged
{
get
{
return new EventHandler(Control_ParentChanged);
}
}
#endregion
#region
IDisposable Members
bool isdisposed;
public
bool IsDisposed { get { return isdisposed; } }
public void Dispose()
{
Control.ParentChanged -= ehParentChanged;
Control.LocationChanged -= ehLocationChanged;
Control.EnabledChanged -= new EventHandler(Control_EnabledChanged);
RemoveInfoEvents();
RemoveFromParent();
CheckEnterLeave(false);
isdisposed = true;
}
#endregion
}
List
<ParentReference> parents = new List<ParentReference>();
ParentReference
GetParentReference(ControlLabel cl)
{
Control Parent = cl.Control.Parent;
if
(Parent == null) return null;
ParentReference
pr = null;
for
(int i = 0; i < parents.Count; i++)
{
if (parents[i].Parent == Parent)
{
pr = parents[i];
break
;
}
}
if
(pr == null)
{
pr = new ParentReference(Parent, this);
parents.Add(pr);
}
pr.Add(cl);
return
pr;
}
class
ParentReference
{
public Control Parent;
LabelProvider
Owner;
public
ParentReference(Control Parent, LabelProvider Owner)
{
this.Parent = Parent;
this
.Parent.Paint += ehPaint;
this
.Owner = Owner;
Parent.MouseMove += new MouseEventHandler(Parent_MouseMove);
Parent.MouseLeave += new EventHandler(Parent_MouseLeave);
Parent.Click += new EventHandler(Parent_Click);
}
#region focus
void
Parent_MouseMove(object sender, MouseEventArgs e)
{
CheckBounds();
}
void
Parent_MouseLeave(object sender, EventArgs e)
{
CheckBounds();
}
void
CheckBounds()
{
Point p = Parent.PointToClient(Control.MousePosition);
foreach
(ControlLabel c in children)
{
c.CheckEnterLeave(p);
}
}
void
Parent_Click(object sender, EventArgs e)
{
foreach (ControlLabel c in GetFocusedControls())
{
c.Click();
}
}
IEnumerable<ControlLabel> GetFocusedControls()
{
for (int i = 0; i < children.Count; i++)
{
ControlLabel c = children[i];
if
(!c.IsDisposed && c.Focused) yield return c;
if
(c.IsDisposed) i--;
}
}
#endregion
#region Paint
PaintEventHandler
ehPaint
{
get
{
return new PaintEventHandler(Parent_Paint);
}
}
void
Parent_Paint(object sender, PaintEventArgs e)
{
foreach (ControlLabel cl in children)
{
cl.Paint(e.Graphics);
}
}
public
void Invalidate(ControlLabel cl)
{
if (cl.BoundsSet)
Parent.Invalidate(new Region(cl.Bounds));
else
Invalidate();
}
///
<summary>
///
Invalidates all current labels
///

public
void Invalidate()
{
Region r = new Region();
foreach
(ControlLabel cl in children)
{
if (cl.BoundsSet)
{
r.Union(cl.Bounds);
}
else

{
Parent.Invalidate();
return
;
}
}
Parent.Invalidate(r);
}
#endregion
#region Children
List<ControlLabel> children = new List<ControlLabel>();
public
void Add(ControlLabel cl)
{
children.Add(cl);
Invalidate(cl);
}
public
void Remove(ControlLabel cl)
{
children.Remove(cl);
Invalidate(cl);
if
(children.Count == 0)
{
Dispose();
}
}
#endregion
#region tooltip
ToolTip
tt;
public void ShowTooltip(ControlLabel c, Point p)
{
if (tt == null)
{
tt = new ToolTip();
tt.ShowAlways = true;
}
tt.Show(c.LabelInfo.Text, Parent, p.X, p.Y, 5000);
//tt.SetToolTip(c.Control, c.LabelInfo.Text);
}
public
void RemoveTooltip(ControlLabel c)
{
if (tt == null) return;
tt.Hide(Parent);
//tt.SetToolTip(c.Control, null);
}
#endregion
public
void Dispose()
{
Parent.Paint -= ehPaint;
Parent.MouseMove -= new MouseEventHandler(Parent_MouseMove);
Parent.MouseLeave -= new EventHandler(Parent_MouseLeave);
Parent.Click -= new EventHandler(Parent_Click);
Owner.parents.Remove(this);
if
(tt != null)
{
tt.Dispose();
tt = null;
}
}
}


List<ControlLabel> list = new List<ControlLabel>();


#endregion
#region
Default settings
private
LabelInfo defsettings = new LabelInfo(null);
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public
LabelInfo DefaultSettings
{
get { return defsettings; }
}
void
defsettings_SettingChanged(object sender, EventArgs e)
{
foreach (ParentReference p in parents)
{
p.Invalidate();
}
}
public
override ISite Site
{
get
{
return base.Site;
}
set

{
defsettings.DesignTime = value != null && value.DesignMode;
base
.Site = value;
}
}
#endregion
#region
Designer
public
class LabelLayoutEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
IWindowsFormsEditorService
iw;
public
override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
iw = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
LayoutPicker
f = new LayoutPicker();
if
(value != null)
f.Result = (LabelLayout)value;
if (context.Instance is LabelInfo)
{
LabelInfo inf = context.Instance as LabelInfo;
if
(inf.DefaultSettings != null && inf.DefaultSettings.Layout != LabelLayout.NotSet)
{
f.RevertValue = inf.DefaultSettings.Layout;
}
}
f.TablePanel.LayoutChosen += new EventHandler<LayoutPicker.LayoutPickerTable.LayoutChosenEventArgs>(TablePanel_LayoutChosen);
iw.DropDownControl(f);
return f.Result;
}
void
TablePanel_LayoutChosen(object sender, LayoutPicker.LayoutPickerTable.LayoutChosenEventArgs e)
{
iw.CloseDropDown();
}
}
#endregion
}
[TypeConverter(typeof(LabelInfo.LabelInfoConverter))]
public
class LabelInfo
{
#region Constructor
public
LabelInfo(string Text)
{
this.Text = Text;
Init();
}
#endregion
#region
setings
enum
LabelInfoSettings
{
Font, Padding, Layout, ForeColor, BackColor, Icon, ShowIcon, ShowText, Cursor, BlinkStyle, BlinkRate, BlinkDuration
}
object
[] settings;
void
Init()
{
Array arr = Enum.GetValues(typeof(LabelInfoSettings));
settings = new object[arr.Length];
}
bool designtime;
internal
bool DesignTime
{
get
{
if (owner != null) return owner.DesignTime;
return
designtime;
}
set

{
if (designtime == value) return;
designtime = value;
if
(designtime)
{
if (blink == null)
blink = new BlinkInfo(this);
}
}
}
object
GetSetting(LabelInfoSettings setting)
{
int i = (int)setting;
if
(settings[i] == null)
{
if (owner != null)
return owner.GetSetting(setting);
return GetDefaultSetting(setting);
}
return
settings[i];
}

T GetSetting<T>(LabelInfoSettings setting)
{
object o = GetSetting(setting);
if
(IsNullValue(setting, o))
return GetDefaultSetting<T>(setting);
return (T)o;
}
object
GetDefaultSetting(LabelInfoSettings setting)
{
switch (setting)
{
case LabelInfoSettings.Font:
return DefaultFont;
case LabelInfoSettings.Padding:
return 5;
case LabelInfoSettings.Layout:
return LabelLayout.Default;
case LabelInfoSettings.ForeColor:
return Color.Black;
case LabelInfoSettings.BackColor:
return Color.Empty;
case LabelInfoSettings.Icon:
return DefaultIcon;
case LabelInfoSettings.ShowIcon:
return false;
case LabelInfoSettings.ShowText:
return true;
case LabelInfoSettings.BlinkDuration:
return 5000;
case LabelInfoSettings.BlinkRate:
return 500;
case LabelInfoSettings.BlinkStyle:
return BlinkStyle.None;
}
return
null;
}
T GetDefaultSetting<T>(LabelInfoSettings setting)
{
return (T)GetDefaultSetting(setting);
}
bool
IsNullValue(LabelInfoSettings setting, object value)
{
if (value == null) return true;
if
(value is ValueType)
{
if (setting == LabelInfoSettings.Layout)
return (LabelLayout)value == LabelLayout.NotSet;
if (value is Color)
return ((Color)value).IsEmpty;
if (value is bool)
return false;
return (int)Convert.ChangeType(value, typeof(int)) == 0;
}
return
false;
}
bool
HasSetting(LabelInfoSettings setting)
{
return settings[(int)setting] != null;
}
void
SetSetting(LabelInfoSettings setting, object value)
{
object cur = GetSetting(setting);
if
(IsNullValue(setting, value))
{
if (cur == null) return;
value = null;
}
else
if (value.Equals(cur))
return;
settings[(int)setting] = value;
OnSettingChanged();
}
void
ResetSetting(LabelInfoSettings setting)
{
settings[(int)setting] = owner == null ? GetDefaultSetting(setting) : null;
}
bool
ShouldSerializeSetting(LabelInfoSettings setting)
{
object o = settings[(int)setting];
return
!IsNullValue(setting, o);
}

public event EventHandler SettingChanged;
void
OnSettingChanged()
{
if (SettingChanged != null)
SettingChanged(this, EventArgs.Empty);
}
#endregion
#region Text
private
string text;
[NotifyParentProperty(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public
string Text
{
get { return text; }
set

{
if (value != null && value.Trim().Length == 0)
text = null;
else
text = value;
if (TextChanged != null)
TextChanged(this, EventArgs.Empty);
OnSettingChanged();
}
}
public
override string ToString()
{
return text;
}
public
event EventHandler TextChanged;
#endregion
#region
Layout
[AmbientValue(LabelLayout.NotSet)]
public
LabelLayout Layout
{
get
{
return GetSetting<LabelLayout>(LabelInfoSettings.Layout);
}
set

{
SetSetting(LabelInfoSettings.Layout, value);
}
}
bool ShouldSerializeLayout()
{
return ShouldSerializeSetting(LabelInfoSettings.Layout);
}

#endregion
#region
Color
///
<summary>
///
The font color. NB: setting this value will overwrite the <see cref="ForeBrush"/> setting
///

[AmbientValue(typeof(Color), "Empty")]
public
Color Color
{
get
{
return GetSetting<Color>(LabelInfoSettings.ForeColor);
}
set

{
SetSetting(LabelInfoSettings.ForeColor, value);
brush = null;
}
}
bool
ShouldSerializeColor()
{
return ShouldSerializeSetting(LabelInfoSettings.ForeColor);
}
void
ResetColor()
{
ResetSetting(LabelInfoSettings.ForeColor);
}

[AmbientValue(typeof(Color), "Empty")]
public
Color BackColor
{
get
{
return GetSetting<Color>(LabelInfoSettings.BackColor);
}
set

{
SetSetting(LabelInfoSettings.BackColor, value);
backbrush = null;
}
}
bool
ShouldSerializeBackColor()
{
return ShouldSerializeSetting(LabelInfoSettings.BackColor);
}
void
ResetBackColor()
{
ResetSetting(LabelInfoSettings.BackColor);
}
#endregion
#region
Brush

private Brush brush, backbrush;
///
<summary>
///
In designtime, a solidbrush can be set through a color picker, but in runtime, the more advanced
///
brushes can be set as well
///

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[AmbientValue(null)]
public
Brush ForeBrush
{
get
{
if (brush == null)
{
if (owner == null)
brush = new SolidBrush(Color);
else
return owner.ForeBrush;
}
return
brush;
}
set

{
brush = value;
OnSettingChanged();
}
}
Brush
GetForeBrush()
{
if (brush != null)
return brush;
if (HasSetting(LabelInfoSettings.ForeColor))
return new SolidBrush(Color);
return ForeBrush;
}
///
<summary>
///
In designtime, a solidbrush can be set through a color picker, but in runtime, the more advanced
///
brushes can be set as well
///

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[AmbientValue(null)]
public
Brush BackBrush
{
get
{
if (backbrush == null && owner != null)
return owner.BackBrush;
return backbrush;
}
set

{
backbrush = value;
OnSettingChanged();
}
}
Brush
GetBackBrush()
{
if (backbrush != null)
return backbrush;
if (HasSetting(LabelInfoSettings.BackColor))
return new SolidBrush(BackColor);
if (owner != null)
return owner.GetBackBrush();
return BackBrush;
}
#endregion
#region
Bounds

static bool HasLayout(LabelLayout layoutFlags, LabelLayout CheckBit)
{
return (layoutFlags & CheckBit) == CheckBit;
}
const
int IconPadding = 0;
public
void Paint(Graphics g, RectangleF rect,bool Enabled)
{
Brush b = GetBackBrush();
if
(b != null)
g.FillRectangle(b, rect);
if (ShowIcon)
{
Point p = new Point((int)rect.X, (int)(rect.Y + (rect.Height - Icon.Height) * .5));
if
(Enabled)
g.DrawIcon(Icon, p.X, p.Y);
else
ControlPaint.DrawImageDisabled(g, Icon.ToBitmap(), p.X, p.Y, Color.Empty);

rect.Offset(Icon.Width + IconPadding, 0);
}
if
(ShowText)
{
if (Enabled)
g.DrawString(text, Font, GetForeBrush(), rect);
else
ControlPaint.DrawStringDisabled(g, text, Font, Color.LightGray, rect, null);
}
}
/// <summary>
///
Calculates where the label should be situated on its parent
///

///
<param name="g">
///
<returns>
public
RectangleF GetBounds(Graphics g, Rectangle RelateTo)
{
SizeF
sizeI = ShowIcon ? Icon.Size : new SizeF(),
size = ShowText ? g.MeasureString(text, Font) : new SizeF();
if (!sizeI.IsEmpty)
{
if (size.IsEmpty)
size = sizeI;
else
{
size.Width += IconPadding + sizeI.Width;
if
(sizeI.Height > size.Height) size.Height = sizeI.Height;
}
}
return GetBounds(size, RelateTo, Padding, Layout);
}
public
static RectangleF GetBounds(SizeF size, Rectangle RelateTo, int padding, LabelLayout layout)
{
PointF pf = RelateTo.Location;
int

h = RelateTo.Height,
w = RelateTo.Width;
if
(HasLayout(layout, LabelLayout.Left))
pf.X -= padding + size.Width;
else if (HasLayout(layout, LabelLayout.Right))
pf.X += padding + w;
else if (HasLayout(layout, LabelLayout.Center))
pf.X += (w - size.Width) / 2;
else if (HasLayout(layout, LabelLayout.RightFromCenter))
pf.X += w - size.Width;
if (HasLayout(layout, LabelLayout.Top))
pf.Y -= padding + size.Height;
else if (HasLayout(layout, LabelLayout.Bottom))
pf.Y += h + padding;
else if (HasLayout(layout, LabelLayout.Middle))
pf.Y += (h - size.Height) / 2;
else if (HasLayout(layout, LabelLayout.UnderMiddle))
pf.Y += h - size.Height;

return new RectangleF(pf, size);
}
public
static void PaintSymbol(Graphics g, Rectangle bounds, LabelLayout Layout)
{
float perc = .50f;
RectangleF
rect = bounds;
rect.Width *= perc;
rect.Height *= perc;
rect.X = (bounds.Width - rect.Width) / 2;
rect.Y = (bounds.Height - rect.Height) / 2;
int
PenWidth = 2;
Rectangle
r = Rectangle.Round(rect);
g.DrawRectangle(new Pen(Color.Navy, PenWidth), r);
SizeF
size = rect.Size;
perc = .3f;
size.Width *= perc;
size.Height *= perc;
rect = LabelInfo.GetBounds(size, r, PenWidth + 1, Layout);
r = Rectangle.Round(rect);
g.DrawRectangle(new Pen(Color.YellowGreen, PenWidth), r);
}
#endregion
#region
defaults
private
LabelInfo owner;
///
<summary>
///
For design time support, the owner is set to determine the defaults
///

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public
LabelInfo DefaultSettings
{
get { return owner; }
set
{ owner = value; }
}
#endregion
#region
Font
static
Font DefaultFont = new Font("Times New Roman", 10);
[AmbientValue(null)]
public
Font Font
{
get
{
return GetSetting<Font>(LabelInfoSettings.Font);
}
set

{
SetSetting(LabelInfoSettings.Font, value);
}
}
bool
ShouldSerializeFont()
{
return ShouldSerializeSetting(LabelInfoSettings.Font);
}
void
ResetFont()
{
ResetSetting(LabelInfoSettings.Font);
}
#endregion
#region
Cursor
[AmbientValue(null)]
public
Cursor Cursor
{
get
{
return GetSetting<Cursor>(LabelInfoSettings.Cursor);
}
set

{
SetSetting(LabelInfoSettings.Cursor, value);
}
}
bool
ShouldSerializeCursor()
{
return ShouldSerializeSetting(LabelInfoSettings.Cursor);
}
void
ResetCursor()
{
ResetSetting(LabelInfoSettings.Cursor);
}
#endregion
#region
Padding
///
<summary>
///
The amount of space that should exist between the end of the text and the control
///

[AmbientValue(-1)]
public
int Padding
{
get
{
return GetSetting<int>(LabelInfoSettings.Padding);
}
set

{
SetSetting(LabelInfoSettings.Padding, value);
}
}
bool
ShouldSerializePadding()
{
return ShouldSerializeSetting(LabelInfoSettings.Padding);
}
void
ResetPadding()
{
ResetSetting(LabelInfoSettings.Padding);
}
#endregion
#region
Icon
[System.Runtime.InteropServices.DllImport("shell32.dll")]
extern
static IntPtr ExtractIcon(int hInst, string lpszExeFileName, int nIconIndex);
static
Icon GetIcon(int index)
{
//IntPtr handle = ExtractIcon(0, "user32.dll", index);
IntPtr
handle = ExtractIcon(0, "shell32.dll", index);
Icon
ic = Icon.FromHandle(handle);
Bitmap
bmp = new Bitmap(16, 16);
Graphics
.FromImage(bmp).DrawIcon(ic, new Rectangle(0, 0, bmp.Width, bmp.Height));
return
Icon.FromHandle(bmp.GetHicon());
}
static
Icon DefaultIcon = GetIcon(73);

[AmbientValue(null)]
public
Icon Icon
{
get
{
return GetSetting<Icon>(LabelInfoSettings.Icon);
}
set

{
SetSetting(LabelInfoSettings.Icon, value);
}
}
bool
ShouldSerializeIcon()
{
return ShouldSerializeSetting(LabelInfoSettings.Icon);
}
void
ResetIcon()
{
ResetSetting(LabelInfoSettings.Icon);
}
#endregion
#region
ShowIcon
[AmbientValue(false)]
public
bool ShowIcon
{
get
{
return GetSetting<bool>(LabelInfoSettings.ShowIcon);
}
set

{
SetSetting(LabelInfoSettings.ShowIcon, value);
}
}
bool
ShouldSerializeShowIcon()
{
return ShouldSerializeSetting(LabelInfoSettings.ShowIcon);
}
void
ResetShowIcon()
{
ResetSetting(LabelInfoSettings.ShowIcon);
}
#endregion
#region
ShowText
[AmbientValue(true)]
public
bool ShowText
{
get
{
return GetSetting<bool>(LabelInfoSettings.ShowText);
}
set

{
SetSetting(LabelInfoSettings.ShowText, value);
}
}
bool
ShouldSerializeShowText()
{
return ShouldSerializeSetting(LabelInfoSettings.ShowText);
}
void
ResetShowText()
{
ResetSetting(LabelInfoSettings.ShowText);
}
#endregion
#region
Blink
BlinkInfo blink;
[AmbientValue(null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public
BlinkInfo Blink
{
get
{
if (blink == null)
{
blink = new BlinkInfo(this);
}
return
blink;
}
}
bool
ShouldSerializeBlink()
{
return
ShouldSerializeSetting(LabelInfoSettings.BlinkStyle)
|| ShouldSerializeSetting(LabelInfoSettings.BlinkRate)
|| ShouldSerializeSetting(LabelInfoSettings.BlinkDuration);
}
[AmbientValue(null)]
[TypeConverter(typeof(ExpandableObjectConverter))]
public
class BlinkInfo
{
public readonly LabelInfo Owner;
public
BlinkInfo(LabelInfo Owner)
{
this.Owner = Owner;
}
#region Style
[AmbientValue(true)]
public
BlinkStyle Style
{
get
{
return Owner.GetSetting<BlinkStyle>(LabelInfoSettings.BlinkStyle);
}
set

{
Owner.SetSetting(LabelInfoSettings.BlinkStyle, value);
}
}
bool
ShouldSerializeStyle()
{
return Owner.ShouldSerializeSetting(LabelInfoSettings.BlinkStyle);
}
void
ResetStyle()
{
Owner.ResetSetting(LabelInfoSettings.BlinkStyle);
}
#endregion
#region
Rate
[AmbientValue(0)]
public
int Rate
{
get
{
return Owner.GetSetting<int>(LabelInfoSettings.BlinkRate);
}
set

{
Owner.SetSetting(LabelInfoSettings.BlinkRate, value);
}
}
bool
ShouldSerializeRate()
{
return Owner.ShouldSerializeSetting(LabelInfoSettings.BlinkRate);
}
void
ResetRate()
{
Owner.ResetSetting(LabelInfoSettings.BlinkRate);
}
#endregion
#region
Duration
[AmbientValue(0)]
public
int Duration
{
get
{
return Owner.GetSetting<int>(LabelInfoSettings.BlinkDuration);
}
set

{
Owner.SetSetting(LabelInfoSettings.BlinkDuration, value);
}
}
bool
ShouldSerializeDuration()
{
return Owner.ShouldSerializeSetting(LabelInfoSettings.BlinkDuration);
}
void
ResetDuration()
{
Owner.ResetSetting(LabelInfoSettings.BlinkDuration);
}
#endregion
public override string ToString()
{
if (Style == BlinkStyle.None) return "None";
string
res = "Blink every " + Rate + " milliseconds";
if
(Style == BlinkStyle.LimitedTime) res += " for a timespan of " + Duration + " milliseconds";
return
res;
}
}
#endregion
#region
Type Converter
public
class DefaultInstanceConverter<T> : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return base.CanConvertTo(context, destinationType)
|| destinationType == typeof(InstanceDescriptor);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (value is T && destinationType == typeof(InstanceDescriptor))
{
return GetInstanceDescriptor((T)value);
}
return
base.ConvertTo(context, culture, value, destinationType);
}
protected virtual InstanceDescriptor GetInstanceDescriptor(T obj)
{
ConstructorInfo ci = obj.GetType().GetConstructor(Type.EmptyTypes);
if
(ci == null)
throw new Exception(obj.GetType().FullName + " does not contain parameterless constructors");
return new InstanceDescriptor(ci, null, false);
}
}
public class LabelInfoConverter : DefaultInstanceConverter<LabelInfo>
{
protected override InstanceDescriptor GetInstanceDescriptor(LabelInfo li)
{
if (string.IsNullOrEmpty(li.text)) return null;
bool
isComplete = true;
foreach
(LabelInfoSettings s in Enum.GetValues(typeof(LabelInfoSettings)))
{
if (li.ShouldSerializeSetting(s))
{
isComplete = false;
break
;
}
}
return
new InstanceDescriptor(
typeof(LabelInfo).GetConstructor(new Type[] { typeof(string) }),
new object[] { li.text }, isComplete);
}

public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return base.CanConvertFrom(context, sourceType)
|| sourceType == typeof(string);
}
public
override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
Control c = context.Instance as Control;
if
(c != null && value is string)
{
string txt = value as string;
LabelInfo
li = context.PropertyDescriptor.GetValue(c) as LabelInfo;
if
(li == null)
{
li = new LabelInfo(txt);
context.PropertyDescriptor.SetValue(c, li);
}
else
li.Text = txt;
return li;
}
return
base.ConvertFrom(context, culture, value);
}
public
override bool IsValid(ITypeDescriptorContext context, object value)
{
LabelInfo inf = value as LabelInfo;
if
(inf == null) return false;
return
!string.IsNullOrEmpty(inf.text);
}
}
#endregion
}

public enum BlinkStyle
{
None,
Permanent,
LimitedTime
}
[Flags]
[Editor(typeof(LabelProvider.LabelLayoutEditor), typeof(UITypeEditor))]
public
enum LabelLayout
{
NotSet = 0,
Top = 1,
AboveMiddle = 2,
Middle = 4,
UnderMiddle = 8,
Bottom = 16,
Left = 32,
LeftFromCenter = 64,
Center = 128,
RightFromCenter = 256,
Right = 512,
Default = LabelLayout.Left | LabelLayout.AboveMiddle
}
#region
LayoutPicker
///
<summary>
///
Control to select a <see cref="LabelLayout"/>. Used in design time for LabelLayout properties
///

[ToolboxItem(false)]
public
class LayoutPicker : UserControl
{
public LayoutPicker()
{
table.Dock = DockStyle.Fill;
Controls.Add(table);
}
LayoutPickerTable
table = new LayoutPickerTable();
[Browsable(false)]
public
LayoutPickerTable TablePanel
{
get
{
return table;
}
}
[DefaultValue(LabelLayout.Default)]
public
LabelLayout Result
{
get { return table.Result; }
set
{ table.Result = value; }
}
LabelLayout
revertvalue;
[DefaultValue(LabelLayout.NotSet)]
public
LabelLayout RevertValue
{
get { return revertvalue; }
set

{
revertvalue = value;
if
((revertvalue == LabelLayout.NotSet) == (lblRevert == null))
return;
if (lblRevert == null)
{
lblRevert = new Label();
lblRevert.ForeColor = Color.Blue;
lblRevert.Dock = DockStyle.Bottom;
lblRevert.Font = new Font(lblRevert.Font, FontStyle.Underline);
lblRevert.Click += new EventHandler(lblRevert_Click);
lblRevert.Text = "Set to parent";
lblRevert.TextAlign = ContentAlignment.MiddleCenter;
lblRevert.Cursor = Cursors.Hand;
Controls.Add(lblRevert);
}
else

{
lblRevert.Dispose();
lblRevert = null;
}
}
}
void
lblRevert_Click(object sender, EventArgs e)
{
Result = revertvalue;
table.InvokeLayoutChosen(false);
}
Label
lblRevert;
public
class LayoutPickerTable : TableLayoutPanel
{
Label MiddleControl = new Label();
int
cnt = 5;
public
LayoutPickerTable()
: this(LabelLayout.Default)
{
}
public
LayoutPickerTable(LabelLayout Current)
{
res = startvalue = Current;
RowCount = ColumnCount = cnt;
float
perc = 100 / cnt;
for
(int i = 0; i < cnt; i++)
{
ColumnStyles.Add(new ColumnStyle(SizeType.Percent, perc));
RowStyles.Add(new RowStyle(SizeType.Percent, perc));
}

for (int i = 0; i < cnt; i++)
{
AddButton(i, 0);
AddButton(i, cnt - 1);
if
(i > 0 && i < cnt - 1)
{
AddButton(0, i);
AddButton(cnt - 1, i);
}
}

MiddleControl.Dock = DockStyle.Fill;
MiddleControl.BackColor = Color.Gray;
MiddleControl.TextAlign = ContentAlignment.BottomCenter;
Controls.Add(MiddleControl, 1, 1);
SetRowSpan(MiddleControl, 3);
SetColumnSpan(MiddleControl, 3);
MiddleControl.Paint += new PaintEventHandler(MiddleControl_Paint);
}
void
Navigate(int Col, int Row, LayoutButton b)
{
TableLayoutPanelCellPosition cell = GetCellPosition(b);
Navigate(Col, Row, cell.Column, cell.Row);
}
void
Navigate(int Col, int Row, int curcol, int currow)
{
currow = Math.Max(0, Math.Min(cnt - 1, Row + currow));
curcol = Math.Max(0, Math.Min(cnt - 1, Col + curcol));
LayoutButton
b = GetControlFromPosition(curcol, currow) as LayoutButton;
if
(b == null)
{
Navigate(Col, Row, curcol, currow);
}
else
b.Focus();
}
protected
override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Cancel)
{
Cancel();
}
}
void
Cancel()
{
Result = startvalue;
OnLayoutChosen(true);
}

void MiddleControl_Paint(object sender, PaintEventArgs e)
{
LabelInfo.PaintSymbol(e.Graphics, MiddleControl.Bounds, res);
}
LabelLayout
startvalue;
class
LayoutButton : Button
{
public new readonly LabelLayout Layout;
public
LayoutButton(LabelLayout Layout)
{
this.Layout = Layout;
}
public
override Color BackColor
{
get
{
if (Selected)
return Color.Gray;
return Control.DefaultBackColor;
}
set

{
//base.BackColor = value;
}
}
public
bool Selected
{
get
{
if (Parent == null) return false;
return
Owner.res == Layout;
}
}
LayoutPickerTable
Owner
{
get { return Parent as LayoutPickerTable; }
}
protected
override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Down:
Owner.Navigate(0, 1, this);
break
;
case Keys.Up:
Owner.Navigate(0, -1, this);
break
;
case Keys.Left:
Owner.Navigate(-1, 0, this);
break
;
case Keys.Right:
Owner.Navigate(1, 0, this);
break
;
default:
return base.IsInputKey(keyData);
}
return
true;
}
}


void AddButton(int Column, int Row)
{
LabelLayout lay = GetLayout(Column, Row);
LayoutButton
b = new LayoutButton(lay);
b.Dock = DockStyle.Fill;
b.TabStop = false;
b.Click += new EventHandler(b_Click);
Controls.Add(b, Column, Row);
buttons.Add(b);
}

LabelLayout GetLayout(int col, int row)
{
res = 0;
if
(col == 0)
res |= LabelLayout.Left;
else if (col == cnt - 1)
res |= LabelLayout.Right;
else
res |= (LabelLayout)((int)LabelLayout.LeftFromCenter << col - 1);
if (row == 0)
res |= LabelLayout.Top;
else if (row == cnt - 1)
res |= LabelLayout.Bottom;
else
res |= (LabelLayout)((int)LabelLayout.AboveMiddle << row - 1);
return res;
}
void
b_Click(object sender, EventArgs e)
{
LayoutButton b = sender as LayoutButton;
Result = b.Layout;
OnLayoutChosen(false);
}
void
OnLayoutChosen(bool Cancelled)
{
if (LayoutChosen != null)
LayoutChosen(this, new LayoutChosenEventArgs(Result, Cancelled));
}
public
void InvokeLayoutChosen(bool Cancelled)
{
OnLayoutChosen(Cancelled);
}
public
class LayoutChosenEventArgs : EventArgs
{
public readonly bool Cancelled;
public
readonly LabelLayout Result;
public
LayoutChosenEventArgs(LabelLayout Result, bool Cancelled)
{
this.Result = Result;
this
.Cancelled = Cancelled;
}
}
public
event EventHandler<LayoutChosenEventArgs> LayoutChosen;
LabelLayout
res;
List
<LayoutButton> buttons = new List<LayoutButton>();
public
LabelLayout Result
{
get { return res; }
set

{
LayoutButton b = GetSelectedButton();
res = value;
MiddleControl.Text = res.ToString();
if
(b != null)
b.Invalidate();
if ((b = GetSelectedButton()) != null)
b.Invalidate();
}
}

LayoutButton GetSelectedButton()
{
foreach (LayoutButton btn in buttons)
{
if (btn.Selected)
return btn;
}
return
null;
}
}
}
#endregion

}
. . .
posted @ 4:33 AM