HotDog's Blog

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

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

SepOctober 2006Nov
SMTWTFS
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

Articles

Archives

Topics

CONTACT

Fun but useful linkies

General

VS 2005

Wolfenstein ET

Thursday, October 26, 2006 #

Back to main post (including source): http://blogs.vbcity.com/hotdog/archive/2006/10/26/6568.aspx

How the control came to be
(this is just the thoughts of the last couple of days written down and most likely will only ever read by people who are bored ;-) )
Everyone who worked with Access generally misses at least 2 things when working in other environments: the report engines and continuous forms (or subforms). Although when intensively used, Access may have exposed many flaws, those are 2 items that made up for a lot of them.
In .net you can easily enough use docking to create a list of controls, but when giving them the proper position, you have to use the Z order which is not the fastest solution. And yes, there are the master detail solutions, but let's be honest, they often just aren't the way you want to represent your data.
There were 3 major points for the creation of the continuous control:
1. it had to be usable with the default .net BindingSource component (through the bindingsource any datasource can be used)
2. it had to be fast with large lists, which meant that the representation only had to be created when either the record was selected or if the record was inside the viewable area.
3. it had to be usable by simply dropping controls on it in designtime, so that the user is not obliged to create a custom control for each subform

In my view, if the points above could be accomplished, the combination of the .net databinding and all the other .net functionality would definately make the wanting of other types of subforms a thing of the past. (Only the report engine is still something. Anyone interested in starting that project? :-D )
Although I did find some other continuous form projects on the web, they just didn't suit my needs (as I'm sure this control will not suit many of your needs :p )
At first I was trying to add controls for the entire viewable area. That meant that multiple copies of the original controls were used. Now that gave problems with point 3: it was easily doable for predefined controls, using a single controltype, but for design time drag and drop functionality, that would mean copying the original controls. To do that, the only way seemed to be using reflection to copy all fields and properties. That in turn gave the risk of shallow copies. All in all: reflection was too dangerous on itself.
Then came using the CodeDomSerializer to catch that part of the designer that created the code that created the first control instances. That seemed to be promising, but gave all sorts of other headaches. It is still an avenue I'd like to finish some day.
But still, since only one control can be focused at a time, I found it a bit of waste to create multiple complex controls of the same type.
Then suddenly a vague mist disappeared and the sounds weren't so muffled anymore. From within those mists a voice spoke: “Remember, remember“. Although I like the Lion king, I was wondering why a subconcious voice would start quoting from that film when dealing with coding. But then a memory did come: where the previous .net versions didn't have that possibility, .net 2.0 had the ability to paint the graphical representation of a control to a bitmap.
Effectively that meant, that if the information had to be shown to the user, the bindingsource's position had to be changed temporarily to update the control, paint that information to a bitmap, and show the image when needed. When clicked on it, the position would be set the index it represented and the actual detail information control(s) would be focused.
In the end, that was a lot easier than expected. (bigger problems occured in getting the designer to do as wanted) and the link to the source code represents the result.

All in all it still has a couple of disadvantages:
1. It must be bound to a bindingsource
2. All editable controls must be databound to show different information per record
Now imho these aren't really disadvantages, since they fit exactly in the scenario for which the control was created, but it can complicate some uses.
The first 'problem' is the smallest: a datasource can be almost any type. you can quickly asign an array or a (generic) list. even if you just asign an arraylist, you can display the 'subform' as many times as fit in the list. And of course all other normal data sources can be used as well. If you tend to use a particular datasource often, I'd suggest adding a property that automatically creates the encapsulating bindingsource for that collection.
About the second issue: if you add an unbound label, the label will display the same text for each record. Since it's a label, that's probably exactly what it's supposed to do. But if you add an unbound textbox, the state of that textbox will not be remembered per record. Rather whatever you enter will be taken with it to the next record you focus. Of course that's logical, because there's no underlying data for the textbox if it's unbound and so there is only one instance of the text it represents. So you'll need to bind it to the source, but again: that's exactly what a subform is supposed to do ;-)
One other small issue that might occur: some controls don't fully support the drawtobitmap functionality. An example of such a control is the richtextbox. But since these types of controls normally won't be used for data editing, that most likely won't be a problem either.

posted @ 9:30 AM

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

Code CopyHideScrollFull
#define IncludeDesignTimeSupport

using
System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Drawing;
using
System.Text;
using
System.Windows.Forms;


//Legal mumbo jumbo:
//(c) 2006 R.Verpalen, Subro Software
//the control and code can be freely used, but the source may only be distributed through a site of the creator
//at this time, that means blogs.vbcity.com/hotdog
//That also means personal alterations are for your own use only ;-)

namespace
Subro.Controls
{
/// <summary>
///
Continuous control: drag any control or number of controls onto it's design surface, attach
///
a bindingsource and a continous 'subform' is the result
///
</summary>
///
<remarks>
///
Needed references:
///
     the default windows forms: System,System.Drawing,System.Windows.Forms
///
     for design time functionality: System.Design
///
Although the class can't be designed directly (due to the way
///
visual studio handles the designer creation), but inheriting classes
///
and instances dropped onto another designer support full design
///
time functionality
///
</remarks>
///
#if IncludeDesignTimeSupport
[Designer(typeof(ContinuousControlDesigner))]
[Designer(typeof(ContinuousControlDocumentDesigner), typeof(System.ComponentModel.Design.IRootDesigner))]
#endif
[DefaultProperty("BindingSource")]
public
partial class ContinuousControl : Panel, ISupportInitialize
{
#region constructor
Panel scrollpanel = new Panel();
public ContinuousControl()
{
Init();
}
void Init()
{
this.Name = "ContinuousControl";
this
.MinimumSize = new Size(100, 24);
base
.BorderStyle = DefaultBorderStyle;
scrollpanel.AutoScroll = true;
scrollpanel.Dock = DockStyle.Fill;
scrollpanel.BackColor = Color.Gray;
Controls.Add(scrollpanel);
mainpanel = new MainPanel(this);
instance = new ControlInstance(this);
this
.NavigatorStyle = GetDefaultNavigatorStyle();
valfailedaction = GetDefaultValidationFailedAction();
}

#endregion
#region designer support
//this region contains the small designer part that's always
//included for easy compilation

//the actual design time support is in the partial class further down

//and can be omitted if wanted for the final release.   
//properties windows category constants
protected
const string CategoryPrefix = " ";
protected
const string DataCategory = CategoryPrefix + "Data";
protected
const string ItemCategory = CategoryPrefix + "Item settings";
protected
const string NavigatorCategory = CategoryPrefix + "Navigator";
protected
const string HeaderCategory = CategoryPrefix + "Header";
protected
const string BehaviorCategory = CategoryPrefix + "Behavior";

#if
IncludeDesignTimeSupport
/// <summary>
///
Only used for design time functionality for inheritors
///
</summary>
protected
IComponent _Instance;
#endif
#endregion
#region supporting controls
ControlInstance instance;
/// <summary>
///
Direct access to the controls of the main record instance.
///
Controls added in design time are also serialized through this property
///
</summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public
ControlCollection InstanceControls
{
get { return instance.Controls; }
}

#if
IncludeDesignTimeSupport
[Designer(typeof(InstanceDesigner))]
[System.ComponentModel.Design.Serialization.DesignerSerializer(
typeof(System.ComponentModel.Design.Serialization.CodeDomSerializer),
typeof
(System.ComponentModel.Design.Serialization.CodeDomSerializer))] //this serializer is attached to make sure the inheriting classes will not serialize properties of the protected instance
#endif
class ControlInstance : Control
{
public readonly ContinuousControl owner;
public
ControlInstance(ContinuousControl Owner)
{
BackColor = Owner.BackColor;
Owner.mainpanel.Controls.Add(this);
this
.owner = Owner;
}
Brush nonfocusbrush;
public
override Color BackColor
{
get
{
return base.BackColor;
}
set

{
base.BackColor = value;
nonfocusbrush = new System.Drawing.Drawing2D.HatchBrush(
System.Drawing.Drawing2D.HatchStyle.Percent05 | System.Drawing.Drawing2D.HatchStyle.LightUpwardDiagonal,
ControlPaint.Light(value),
value);
}
}
public bool ChildPaint;
/*
protected override void OnPaintBackground(PaintEventArgs pevent)
{
if (ChildPaint && !DesignMode)
pevent.Graphics.FillRectangle(nonfocusbrush, ClientRectangle);
else
{
base.OnPaintBackground(pevent);                    
//ControlPaint.DrawBorder(pevent.Graphics, ClientRectangle, Color.Navy, ButtonBorderStyle.Solid);
}
}*/
public ContinuousControl Owner
{
get { return owner; }
}
protected override Size DefaultSize
{
get
{
return owner == null ? Size.Empty : owner.ItemSize;
}
}
object Current
{
get { return owner.source.Current; }
}
protected override void OnLayout(LayoutEventArgs levent)
{
if (!owner.IsInitializing)
Reset();
base.OnLayout(levent);
}
void Reset()
{
owner.ResetItemSize();
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
Owner.mainpanel.CheckDragStart();
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
Owner.mainpanel.CheckDragStart();
base.OnMouseMove(e);
}

internal bool Validate()
{
owner.source.EndEdit();
Focus();
if
(!Focused)
{
bool AllowContinue;
owner.OnRecordValidationFailed(out AllowContinue);
return
AllowContinue;
}
return
true;
}
internal void CancelCurrentEdit()
{
owner.source.CurrencyManager.CancelCurrentEdit();
}
}
bool suspendlocationchange;
///
<summary>
///
Sets the focus control to the proper position.
///
This procedure is executed when the <see cref="BindingSource"/>'s
///
position is changes or when a layout property is changed
///
</summary>
void
SetInstanceLocation()
{
if (IsInitializing) return;
#if IncludeDesignTimeSupport
if (this.DesignMode)
{
SetDesignTimeInstanceSize();
}
else
#endif
if (instance != null && !suspendlocationchange)
{
SuspendLayout();
InvalidateSelectionArea(instance.Bounds);
bool
vis = SelectedIndex != -1;
instance.Visible = vis;
if
(vis)
{
instance.Bounds = GetNonPaddedItemBounds(SelectedIndex);
scrollpanel.ScrollControlIntoView(instance);
}
InvalidateSelectionArea(instance.Bounds);
ResumeLayout();
}
}


MainPanel mainpanel;
class MainPanel : ContainerControl
{
public readonly ContinuousControl Owner;
public
MainPanel(ContinuousControl Owner)
{
this.Owner = Owner;
Size = Size.Empty;
Height = 0;
Dock = DockStyle.Top;
SetStyle(
ControlStyles.OptimizedDoubleBuffer
| ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint
, true);
Owner.scrollpanel.Controls.Add(this);
}
protected override bool ProcessTabKey(bool forward)
{
if (!Owner.handletabs)
return base.ProcessTabKey(forward);
return ProccessTabKey(forward, ActiveControl);
}
bool ProccessTabKey(bool forward, Control current)
{
bool wrap = forward ? Owner.SelectedIndex < Owner.Count - 1 : Owner.SelectedIndex > 0;
bool
res = SelectNextControl(current, forward, true, true, false);
if (!res && wrap)
{
if (forward)
Owner.SelectedIndex++;
else
Owner.SelectedIndex--;
return SelectNextControl(null, forward, true, true, false);
}
return
res;
}
#region
Paint
public new void Paint(Graphics g)
{
if (Owner.ItemSize.Height == 0)
{
if (Owner.DrawStartInsertButton)
DrawInsertButton(-1, g);
return;
}
Point
p = Owner.scrollpanel.AutoScrollPosition;
p.X *= -1;
p.Y *= -1;
int
from = Owner.GetIndex(p);
p.Offset(Parent.Width, Parent.Height);
int
sel = Owner.SelectedIndex;
int
to = Owner.GetIndex(p) + 1;
if
(to > Owner.Count && !Owner.DesignMode) to = Owner.Count;
for (int i = from; i < to; i++)
{
if (i == -1)
{
//first insert button
DrawInsertButton(i, g);
continue
;
}
Rectangle bounds = Owner.GetNonPaddedItemBounds(i);

bool selected = i == sel;
if (!Owner.DesignMode)
{
if (selected ||
(Owner.multiselect && Owner.GetItem(i).SelectedVersion == Owner.selectionversion))
{
selected = true;
g.FillRectangle(SelectionBrush, Owner.GetSelectionBounds(bounds));
}
if (i != sel)
{
Bitmap bmp = Owner.GetBitmap(i);
g.DrawImage(bmp, bounds.Location);
}
}
else
if (i > 0)
{
g.DrawRectangle(Pens.Black, bounds);
}
//select buttons
if
(Owner.showselectbuttons)
{
DrawSelectButton(bounds, g, selected);
}
//insert buttons
if
(Owner.ShowInsertButtons)
{
bool last = i == Owner.Count - 1;
if
((Owner.DrawInsertButtonsBetweenItems && !last)
|| (Owner.DrawEndInsertButton && last))
DrawInsertButton(i, g);
}
//delete button
if
(Owner.ShowDeleteButtonPerItem)
{
DrawDeleteButton(i, g);
}
//drag insert
if
(Owner.DragInsertIndex == i)
{
DrawDragInsert(i, g);
}
}
if (to == Owner.Count && Owner.DragInsertIndex == to)
DrawDragInsert(to, g);
}
void DrawInsertButton(int Index, Graphics g)
{
Rectangle bounds = Owner.GetInsertButtonBounds(Index);
DrawButton(bounds, g, Owner.insertbuttonicon,
InsertButtonFocused == Index, Owner.source != null && !Owner.source.AllowNew, InsertButtonPushed == Index);
}
void DrawSelectButton(Rectangle bounds, Graphics g, bool IsSelected)
{
if (Owner.lefttoright)
{
bounds.Y = 0;
bounds.Height = SelectButtonWidth;
}
else

{
bounds.X = 0;
bounds.Width = SelectButtonWidth;
}
g.DrawRectangle(new Pen(Owner.instance.BackColor), bounds);
if
(IsSelected)
{
g.DrawImage(Owner.SelectedRecordImage,
bounds.X + (bounds.Width - Owner.SelectedRecordImage.Width) / 2,
bounds.Top + (bounds.Height - Owner.SelectedRecordImage.Height) / 2);
}
}
void DrawButton(Rectangle bounds,
Graphics g,
Icon
ic,
bool
IsSelected, bool IsInActive, bool IsPushed)
{
if (IsInActive)
{
ControlPaint.DrawButton(g, bounds, ButtonState.Inactive);
}
else
if (IsSelected)
{
ControlPaint.DrawButton(g, bounds,
IsPushed
? ButtonState.Pushed
: ButtonState.Normal);
}
else
ControlPaint.DrawBorder(g, bounds, Color.Gray, ButtonBorderStyle.Outset);
g.DrawIcon(ic, bounds.X + (bounds.Width - ic.Width) / 2, bounds.Y + (bounds.Height - ic.Height) / 2);
}
void DrawDeleteButton(int Index, Graphics g)
{
Rectangle bounds = Owner.GetDeleteButtonBounds(Index);
DrawButton(bounds, g, Owner.deletebuttonicon,
Index == DeleteButtonFocused,
Owner.source != null && !Owner.source.AllowRemove,
DeleteButtonPushed == DeleteButtonFocused);
}
protected override void OnPaint(PaintEventArgs e)
{
Paint(e.Graphics);
base
.OnPaint(e);
}
public Brush SelectionBrush = new SolidBrush(SystemColors.Highlight);
#endregion
#region DragDrop
public override bool AllowDrop
{
get
{
return base.AllowDrop;
}
set

{
base.AllowDrop = value;
}
}
bool UseDrag
{
get
{
return AllowDrop || Owner.autodrag;
}
}
Point DragStartPoint;
public void CheckDragStart()
{
if (!UseDrag) return;
Point
p = LocalMousePosition;
int
diff = Math.Abs(DragStartPoint.X - p.X)
+ Math.Abs(DragStartPoint.Y - p.Y);
if (diff > 3)
{
Owner.OnDragStart(Owner.GetIndex(DragStartPoint));
DragStartPoint = Point.Empty;
}
}
protected override void OnDragEnter(DragEventArgs e)
{
Owner.OnDragEnter(e);
}
protected override void OnDragOver(DragEventArgs e)
{
Owner.OnDragOver(e);
}
protected override void OnDragLeave(EventArgs e)
{
Owner.OnDragLeave(e);
}

protected override void OnDragDrop(DragEventArgs e)
{
DragStartPoint = Point.Empty;
Owner.OnDragDrop(e);
}
const int DragInsertHeight = 3;
Rectangle GetDragInsertBounds(int Index)
{
Rectangle res = Owner.GetItemBounds(Index);
float
half = DragInsertHeight / 2f;
if
(Index == 0)
half = 0;
else if (Index == Owner.Count)
half = DragInsertHeight;
if (Owner.lefttoright)
{
res.Y = 0;
res.Height = Height;
res.X -= (int)Math.Ceiling(half);
res.Width = DragInsertHeight;
}
else

{
res.X = 0;
res.Width = Width;
res.Y -= (int)Math.Ceiling(half);
res.Height = DragInsertHeight;
}
return
res;
}
void DrawDragInsert(int Index, Graphics g)
{
g.FillRectangle(Brushes.Yellow, GetDragInsertBounds(Index));
}
public void InvalidateDragInsert(int Index)
{
Invalidate(GetDragInsertBounds(Index));
}
#endregion
public void SetSize()
{
int i = Owner.Count;
if
(i == 0)
Size = Size.Empty;
else
{
Rectangle r = Owner.GetItemBounds(i);
int
diff = 0;
if
(Owner.ShowInsertButtons && !Owner.DrawInsertButtonsBetweenItems)
{
if (Owner.DrawStartInsertButton)
diff += InsertButtonHeight;
if (Owner.DrawEndInsertButton)
diff += InsertButtonHeight;
}
Size = new Size(
Owner.lefttoright ? r.Left + diff : r.Right,
Owner.lefttoright ? r.Bottom : r.Top + diff);
}
}

#region buttons
int DeleteButtonFocused = -1;
int
DeleteButtonPushed = -1;
int
? InsertButtonFocused;
int
? InsertButtonPushed;
int
GetDeleteButtonFocused()
{
Point p = LocalMousePosition;
int
index = Owner.GetIndex(p);
if
(Owner.GetDeleteButtonBounds(index).Contains(p))
return index;
return -1;
}
int
? GetInsertButtonFocused()
{
Point p = LocalMousePosition;
int
index = Owner.GetIndex(p);
if
(Owner.GetInsertButtonBounds(index).Contains(p))
return index;
return null;
}
Point LocalMousePosition
{
get
{
return PointToClient(Control.MousePosition);
}
}
bool CheckButtonFocus()
{
if (Owner.ShowDeleteButtonPerItem)
{
int i = GetDeleteButtonFocused();
if
(i != DeleteButtonFocused)
{
InvalidateDeleteButton();
DeleteButtonFocused = i;
InvalidateDeleteButton();
}
if
(i != -1) return true;
}
if (Owner.ShowInsertButtons)
{
int? i = GetInsertButtonFocused();
if
(i != InsertButtonFocused)
{
InvalidateInsertButton();
InsertButtonFocused = i;
InvalidateInsertButton();
}
if
(i != null) return true;
}
return
false;
}
void InvalidateDeleteButton()
{
if (DeleteButtonFocused != -1)
Invalidate(Owner.GetDeleteButtonBounds(DeleteButtonFocused));
}
void InvalidateInsertButton()
{
if (InsertButtonFocused != null)
Invalidate(Owner.GetInsertButtonBounds(InsertButtonFocused.Value));
}


#endregion
#region Mouse events
protected
override void OnMouseMove(MouseEventArgs e)
{
if (!DragStartPoint.IsEmpty)
{
CheckDragStart();
}
CheckButtonFocus();
base
.OnMouseMove(e);
}
protected override void OnMouseLeave(EventArgs e)
{
if (Owner.ShowDeleteButtonPerItem)
CheckButtonFocus();
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
DeleteButtonPushed = DeleteButtonFocused;
InsertButtonPushed = InsertButtonFocused;
if
(DeleteButtonFocused != -1)
{
InvalidateDeleteButton();
}
else
if (InsertButtonFocused != null)
InvalidateInsertButton();
else
{
if (UseDrag)
DragStartPoint = LocalMousePosition;
SelectPointed();
}
}
base
.OnMouseDown(e);
}


protected override void OnMouseUp(MouseEventArgs e)
{
if (DeleteButtonPushed != -1 && DeleteButtonPushed == DeleteButtonFocused)
{
Owner.source.RemoveAt(DeleteButtonPushed);
}
if
(InsertButtonPushed != null && InsertButtonFocused == InsertButtonPushed)
{
//insert
Owner.OnInsertItem(InsertButtonPushed.Value + 1);
}
DeleteButtonPushed = -1;
InsertButtonPushed = null;
DragStartPoint = Point.Empty;
base
.OnMouseUp(e);
}
#endregion
#region selection
bool IsMultiSelectClick
{
get
{
if (Owner.multiselect)
//return (ModifierKeys & (Keys.Control | Keys.Shift)) > 0;
return
ModifierKeys == Keys.Control;
return false;
}
}
void SelectPointed()
{
Point p = LocalMousePosition;
int
index = Owner.GetIndex(p);

if (IsMultiSelectClick)
{
Owner.SwitchSelect(index);
return
;
}
Owner.SelectedIndex = index;
Owner.instance.Focus();
Rectangle r = Owner.GetItemBounds(index);
p.X -= r.X;
p.Y -= r.Y;
Control
c = GetChild(Owner.instance, Control.MousePosition);
if
(c != null)
{
InvokeOnClick(c, EventArgs.Empty);
c.Focus();
}
}
Control GetChild(Control c, Point p)
{
Control child = c.GetChildAtPoint(c.PointToClient(p));
if
(child == null) return c;
return
GetChild(child, p);
}
#endregion
}
#endregion
#region item collection
List<Item> items = new List<Item>();
class Item
{
public Bitmap Bitmap;
public
int SelectedVersion;
public
void Clear()
{
Bitmap = null;
}
}
void ResetIndex(int Index)
{
if (IsItemCreated(Index))
{
if (Index == SelectedIndex)
{
items[Index].Bitmap = createBitmap();
}
else

{
items[Index].Clear();
mainpanel.Invalidate(GetItemBounds(Index));
}
}
}
bool IsItemCreated(int Index)
{
return Index < items.Count;
}
Item GetItem(int Index)
{
while (items.Count <= Index)
items.Add(new Item());
return items[Index];
}

void ResetItems()
{
items.Clear();
}
#endregion
#region bitmaps


Bitmap GetBitmap(int Index)
{
Item res = GetItem(Index);
if
(res.Bitmap == null)
res.Bitmap = createBitmap(Index);
else if (!VariableSize && res.Bitmap.Size != ItemSize)
{
res.Bitmap = null;
return
GetBitmap(Index);
}
return
res.Bitmap;
}
Bitmap createBitmap(int Index)
{
if (source == null) return null;
int pos = source.Position;
suspendlocationchange = true;
SuspendLayout();
try

{
source.Position = Index;
return
createBitmap();
}
finally

{
source.Position = pos;
suspendlocationchange = false;
ResumeLayout(false);
}
}
Bitmap
createBitmap()
{
try
{
Bitmap bmp = new Bitmap(instance.Width, instance.Height);
instance.ChildPaint = true;
instance.DrawToBitmap(bmp, new Rectangle(Point.Empty, instance.Size));
return
bmp;
}
catch
(Exception ex)
{
return GetErrorBitmap(SelectedIndex, ex);
}
finally

{
instance.ChildPaint = false;
}
}
protected override void OnEnabledChanged(EventArgs e)
{
base.OnEnabledChanged(e);
for
(int i = 0; i < items.Count; i++)
{
items[i].Clear();
}
mainpanel.Invalidate();
}

/*
Bitmap GetEmptyBitmap()
{
Size size = ItemSize;
return new Bitmap(size.Width, size.Height);
}*/
private Bitmap GetErrorBitmap(int Index, Exception ex)
{
Rectangle r = GetItemBounds(Index);
Bitmap
bmp = new Bitmap(r.Width, r.Height);
Graphics
g = Graphics.FromImage(bmp);
Icon
icon = SystemIcons.Error;
g.DrawIcon(icon, 0, 0);
return
bmp;
}

[System.Runtime.InteropServices.DllImport("shell32.dll")]
extern
static IntPtr ExtractIcon(int hInst, string lpszExeFileName, int nIconIndex);
public
static Icon GetIcon(int index)
{
return GetIcon("shell32.dll", index);
}
public
static Icon GetIcon(string dll, int index)
{
IntPtr handle = ExtractIcon(0, 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());
}
public static Bitmap GetTriangleBitmap(bool lefttoright, int width, int height, Brush FillColor)
{
Bitmap res = new Bitmap(width, height);
Graphics
g = Graphics.FromImage(res);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
if
(lefttoright)
{
gp.AddLine(0, 0, width / 2, height);
gp.AddLine(width / 2, height, width, 0);
gp.AddLine(width, 0, 0, 0);
}
else

{
gp.AddLine(0, 0, width, height / 2);
gp.AddLine(width, height / 2, 0, height);
gp.AddLine(0, height, 0, 0);
}
g.FillPath(FillColor, gp);
g.DrawPath(Pens.Black, gp);
return res;
}

#endregion
#region scrolling
[DefaultValue(true)]
public
new bool AutoScroll
{
get { return scrollpanel.AutoScroll; }
set
{ scrollpanel.AutoScroll = value; }
}
[DefaultValue(false)]
public
override bool AutoSize
{
get
{
return base.AutoSize;
}
set

{
if (value == AutoSize) return;
if
(!DesignMode)
{
if (value)
{
scrollpanel.Dock = DockStyle.Fill;
}
else

{
scrollpanel.Dock = DockStyle.Top;
}
}
scrollpanel.AutoSize = value;
base
.AutoSize = value;
}
}

#endregion
#region source
private BindingSource source;
[Category(DataCategory)]
[DefaultValue(null)]
public
BindingSource BindingSource
{
get { return source; }
set

{
if (source != null)
{
source.ListChanged -= new ListChangedEventHandler(source_ListChanged);
source.PositionChanged -= new EventHandler(source_PositionChanged);
}
source = value;
ResetList();
if
(source != null)
{
source.ListChanged += new ListChangedEventHandler(source_ListChanged);
source.PositionChanged += new EventHandler(source_PositionChanged);
}
if
(navigator != null)
navigator.BindingSource = source;
OnBindingSourceChanged();
}
}
public event EventHandler BindingSourceChanged;
protected
virtual void OnBindingSourceChanged()
{
if (BindingSourceChanged != null)
BindingSourceChanged(this, EventArgs.Empty);
}
int previousposition = -1;
void
source_PositionChanged(object sender, EventArgs e)
{
if (suspendlocationchange) return;

if (multiselect)
{
selectionversion++;
if
(previousposition != -1 && ModifierKeys == Keys.Shift)
{
int pos = source.Position;
int
from = Math.Min(pos, previousposition), to = Math.Max(pos, previousposition);
for
(int i = from; i <= to; i++)
{
GetItem(i).SelectedVersion = selectionversion;
}
}
if (items.Count > 200)
mainpanel.Invalidate();
else
{
InvalidateSelected(selectionversion - 1);
InvalidateSelected(selectionversion);
}
}
SetInstanceLocation();
previousposition = source.Position;
}

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public
int SelectedIndex
{
get
{
if (source == null) return -1;
return
source.Position;
}
set

{
if (source != null)
{
Control c = GetActiveControl();
if
(!instance.Validate())
{
return;
}
ResetIndex(SelectedIndex);
source.Position = value;
if
(c != null)
{
c.Select();
}
}
}
}
Control GetActiveControl()
{
Form f = FindForm();
if
(f == null) return null;
return
GetActiveControl(f);
}
Control
GetActiveControl(ContainerControl cc)
{
Control c = cc.ActiveControl;
if
(c is ContainerControl)
return GetActiveControl(c as ContainerControl);
return c;
}
[Browsable(false)]
public
int Count
{
get
{
if (source == null) return 0;
return
source.Count;
}
}
void RefreshItems()
{
if (DesignMode || IsInitializing) return;
ResetItems();
InvalidateMainPanel();
}
public void ResetList()
{
if (DesignMode || IsInitializing) return;
previousposition = -1;
RefreshItems();
OnResetList();
}
protected virtual void OnResetList()
{
}
bool IsBindingSuspended
{
get
{
return source != null && source.IsBindingSuspended;
}
}
int editing;
public
void BeginUpdate()
{
editing++;
}
public void EndUpdate()
{
if (--editing <= 0)
{
editing = 0;
if
(needreset)
{
needreset = false;
ResetList();
}
}
}
bool
needreset;
void source_ListChanged(object sender, ListChangedEventArgs e)
{
if (editing > 0)
{
needreset = true;
return
;
}
if (e.ListChangedType == ListChangedType.ItemAdded)
{
if (IsItemCreated(e.NewIndex))
items.Insert(e.NewIndex, new Item());
mainpanel.SetSize();
}
else
if (e.ListChangedType == ListChangedType.ItemDeleted)
{
if (e.NewIndex < items.Count)
{
items.RemoveAt(e.NewIndex);
mainpanel.Invalidate();
mainpanel.SetSize();
}
else
if (items.Count == 0)
SetInstanceLocation();
}
else
if (e.ListChangedType == ListChangedType.ItemMoved)
{
ResetIndex(e.OldIndex);
ResetIndex(e.NewIndex);
}
else
if (e.ListChangedType == ListChangedType.Reset)
{
ResetList();
}
else
if (e.ListChangedType == ListChangedType.ItemChanged)
{
ResetIndex(e.NewIndex);
}
}
#endregion
#region item sizing
Size? itemsize;
[Category(ItemCategory)]
public
Size ItemSize
{
get
{
if (itemsize == null)
{
itemsize = GetItemSize();
if
(instance != null && !DesignMode)
instance.Size = itemsize.Value;
}
return
itemsize.Value;
}
set

{
if (value.IsEmpty)
itemsize = null;
else
itemsize = value;
}
}
bool
ShouldSerializeItemSize()
{
return FixedItemSize;// itemsize != null && !GetItemBounds().Equals(itemsize.Value);
}
void
ResetItemSize()
{
if (FixedItemSize) return;
itemsize = null;
if
(VariableSize && !DesignMode && SelectedIndex > -1)
ResetIndex(SelectedIndex);
//sizewithbounds = null;
InvalidateMainPanel();
}
[Category(ItemCategory)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public
bool FixedItemSize
{
get { return itemsizemode == ItemSizeMode.Fixed; }
set

{
if (value == FixedItemSize) return;
if
(value)
ItemSizeMode = ItemSizeMode.Fixed;
else
ItemSizeMode = ItemSizeMode.Automatic;
}
}
static Padding DefaultItemPadding = new Padding(1);
private
Padding itemPadding = DefaultItemPadding;
[Category(ItemCategory)]
public
Padding ItemPadding
{
get { return itemPadding; }
set

{
itemPadding = value;
if
(!IsInitializing)
SetInstanceLocation();
else if (DesignMode)
Invalidate();
else
mainpanel.Invalidate();
}
}
bool
ShouldSerializeItemPadding()
{
return itemPadding != DefaultItemPadding;
}
void
ResetItemPadding()
{
itemPadding = DefaultItemPadding;
}
/*
private Size? sizewithbounds;
[Category(ItemCategory)]
public Size ItemSizeWithPadding
{
get
{
if (sizewithbounds == null)
{
sizewithbounds = GetItemSizeWithBounds();
}
return sizewithbounds.Value;
}
}
*/
[Browsable(false)]
public
bool VariableSize
{
get { return itemsizemode == ItemSizeMode.Variable || itemsizemode == ItemSizeMode.VariableUserSizable; }
}
private ItemSizeMode itemsizemode;
[Category(ItemCategory)]
[DefaultValue(ItemSizeMode.Automatic)]
public
ItemSizeMode ItemSizeMode
{
get { return itemsizemode; }
set
{ itemsizemode = value; }
}

Size GetItemSizeWithBounds()
{
Size size = ItemSize;
if
(size.IsEmpty) return size;
size.Width += itemPadding.Horizontal;
size.Height += itemPadding.Vertical;
return
size;
}
Size GetItemSize()
{
//if (controls.Count == 0) return Size.Empty;
Size
size = new Size();
foreach
(Control c in instance.Controls)
{
size.Width = Math.Max(size.Width, c.Right);
size.Height = Math.Max(size.Height, c.Bottom);
}
return
size;
}
int GetItemTop(int Index)
{
if (lefttoright)
{
if (showselectbuttons) return SelectButtonWidth;
return
0;
}
int res;
if
(VariableSize && !DesignMode)
{
if (Index > 0)
{
res = AddReoccuringHeight(GetBitmap(--Index).Height)
+ GetItemTop(Index);
}
else
res = 0;
}
else
res = GetItemHeight() * Index;
if (DrawStartInsertButton)
res += InsertButtonHeight;
return res;
}
int GetItemLeft(int Index)
{
int res = 0;
if
(!lefttoright)
{
if (showselectbuttons)
res += SelectButtonWidth;
return res;
}
if (VariableSize && !DesignMode)
{
if (Index > 0)
{
res = AddReoccuringWidth(GetBitmap(--Index).Width)
+ GetItemLeft(Index);
}
}
else

{
res += GetItemWidth();
res *= Index;
}
if
(DrawStartInsertButton)
res += InsertButtonHeight;

return res;
}
int AddReoccuringWidth(int w)
{
w += itemPadding.Horizontal;
if
(ShowDeleteButtonPerItem) w += DeleteButtonWidth;
if
(lefttoright && DrawInsertButtonsBetweenItems)
{
w += InsertButtonHeight;
}
return
w;
}
int GetItemWidth()
{
return AddReoccuringWidth(ItemSize.Width);
}
int AddReoccuringHeight(int h)
{
h += itemPadding.Vertical;
if
(!lefttoright && DrawInsertButtonsBetweenItems)
{
h += InsertButtonHeight;
}
return
h;
}
int GetItemHeight()
{
return AddReoccuringHeight(ItemSize.Height);
}
/// <summary>
///
Gets which item is at the specified point of the mainpanel
///
</summary>
///
<param name="p"></param>
///
<returns></returns>
int
GetIndex(Point p)
{
int t, xy;
if
(lefttoright)
{
t = GetItemWidth();
xy = p.X;
}
else

{
t = GetItemHeight();
xy = p.Y;
}
if
(ShowInsertButtons && (insertbuttonstyle & InsertButtonStyles.BeginOnly) > 0)
{
xy -= InsertButtonHeight;
}
if
(xy < 0) return -1;
return
xy / t;
}
Size PadSize(Size size)
{
size.Width += itemPadding.Horizontal;
size.Height += itemPadding.Vertical;
return
size;
}
Rectangle GetItemBounds(int Index)
{
return GetItemBounds(Index, 0, 0, true);
}
Rectangle GetItemBounds(int Index, int LeftOffset, int TopOffset, bool padded)
{
Size size;
if
(VariableSize && Index > -1 && GetItem(Index).Bitmap != null)
size = GetItem(Index).Bitmap.Size;
else
size = ItemSize;
if (padded) size = PadSize(size);
return new Rectangle(
GetItemLeft(Index) + LeftOffset,
GetItemTop(Index) + TopOffset
, size.Width
, size.Height);
}
Rectangle
GetNonPaddedItemBounds(int Index)
{
return GetItemBounds(Index, ItemPadding.Left, ItemPadding.Top, false);
}
protected override Size DefaultSize
{
get
{
return new Size(100, 150);
}
}
#endregion
#region Item validation
protected virtual void OnRecordValidationFailed(out bool AllowContinue)
{
if (RecordValidationFailed != null)
{
HandledEventArgs e = new HandledEventArgs(false);
RecordValidationFailed(this, e);
if
(e.Handled)
{
AllowContinue = true;
return
;
}
}
switch
(valfailedaction)
{
case ValidationFailedActions.AskUser:
AllowContinue = MessageBox.Show("The current input does not validate correctly. If you continue to another record the changes will be lost. Do you wish to continue?"
, null, MessageBoxButtons.YesNoCancel) == DialogResult.Yes;
break;
case ValidationFailedActions.DiscardChanges:
AllowContinue = true;
break
;
default:
AllowContinue = false;
break
;
}
}
public
event HandledEventHandler RecordValidationFailed;
public enum ValidationFailedActions
{
AskUser, DiscardChanges, BlockRecordChange
}
private ValidationFailedActions valfailedaction;
[Category(BehaviorCategory)]
public
ValidationFailedActions ValidationFailedAction
{
get { return valfailedaction; }
set
{ valfailedaction = value; }
}
bool
ShouldSerializeValidationFailedAction()
{
return valfailedaction != GetDefaultValidationFailedAction();
}
protected virtual ValidationFailedActions GetDefaultValidationFailedAction()
{
return ValidationFailedActions.BlockRecordChange;
}
#endregion
#region Key handling
bool In(Keys value, params Keys[] keys)
{
return Array.IndexOf<Keys>(keys, value) != -1;
}
protected override bool IsInputKey(Keys keyData)
{
return In(keyData, Keys.Up, Keys.Down, Keys.PageDown, Keys.PageUp, Keys.Escape)
|| base.IsInputKey(keyData);
}
private
bool handletabs = false;
[DefaultValue(false)]
[Description("If true, all records are walked through when tab is pressed before the control loses focus")]
public
bool HandleTabs
{
get { return handletabs; }
set
{ handletabs = value; }
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (source != null)
{
if (keyData == Keys.Up)
{
if (!lefttoright)
SelectedIndex--;
}
else
if (keyData == Keys.Down)
{
if (!lefttoright)
SelectedIndex++;
}
else
if (keyData == Keys.Left)
{
if (lefttoright)
SelectedIndex--;
}
else
if (keyData == Keys.Right)
{
if (lefttoright)
SelectedIndex++;
}
else
if (keyData == Keys.PageUp)
SelectedIndex -= CountPerPage;
else if (keyData == Keys.PageDown)
SelectedIndex += CountPerPage;
else if (keyData == Keys.Escape)
instance.CancelCurrentEdit();
}
return
base.ProcessCmdKey(ref msg, keyData);
}

int CountPerPage
{
get
{
return (int)Math.Ceiling((double)scrollpanel.Height / ItemSize.Height);
}
}
#endregion
#region additional properties
const BorderStyle DefaultBorderStyle = BorderStyle.Fixed3D;
[DefaultValue(DefaultBorderStyle)]
public
new BorderStyle BorderStyle
{
get { return base.BorderStyle; }
set
{ base.BorderStyle = value; }
}
#endregion
#region Navigator
BindingNavigator navigator;
public enum NavigatorStyles
{
None,
Simple,
Extended
}
private NavigatorStyles navstyle;
[Category(NavigatorCategory)]
public
NavigatorStyles NavigatorStyle
{
get { return navstyle; }
set

{
if (navstyle == value) return;
ShowNavigator = value != NavigatorStyles.None;
if
(ShowNavigator)
ShowFullNavigatorOptions = value == NavigatorStyles.Extended;
}
}
bool
ShouldSerializeNavigatorStyle()
{
return navstyle != GetDefaultNavigatorStyle();
}
protected virtual NavigatorStyles GetDefaultNavigatorStyle()
{
return NavigatorStyles.None;
}


[Category(NavigatorCategory)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public
bool ShowNavigator
{
get
{
return navigator != null;
}
set

{
if (value == ShowNavigator) return;
if
(value)
{
navigator = new BindingNavigator(source);
navigator.DeleteItem.Click += new EventHandler(DeleteItem_Click);
navigator.GripStyle = ToolStripGripStyle.Hidden;
SetNavigatormode(false);
navigator.Width = navigator.Height = 20;
//if (!DesignMode)
Controls.Add(navigator);
navstyle = NavigatorStyles.Simple;
}
else

{
navigator.Dispose();
navigator = null;
navstyle = NavigatorStyles.None;
}
}
}
bool
ShouldSerializeShowNavigator()
{
return GetDefaultNavigatorStyle() != NavigatorStyles.None;
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public
BindingNavigator BindingNavigator
{
get { return navigator; }
}
bool showfulloptions;
[Category(NavigatorCategory)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public
bool ShowFullNavigatorOptions
{
get { return showfulloptions; }
set

{
if (showfulloptions == value) return;
if
(!value && navigator == null)
return;
SetNavigatormode(value);
navstyle = value ? NavigatorStyles.Extended : NavigatorStyles.Simple;
}
}
bool
ShouldSerializeShowFullNavigatorOptions()
{
return (GetDefaultNavigatorStyle() == NavigatorStyles.Extended)
!= showfulloptions;
}
private void SetNavigatormode(bool Full)
{
ShowNavigator = true;
showfulloptions = Full;
foreach
(ToolStripItem it in navigator.Items)
{
if (it != navigator.AddNewItem && it != navigator.DeleteItem)
it.Visible = Full;
}
if
(!dockmanualset)
navigator.Dock = GetDefaultNavigatorDock();
}


DockStyle GetDefaultNavigatorDock()
{
if (ShowFullNavigatorOptions) return DockStyle.Bottom;
return
DockStyle.Left;
}

void DeleteItem_Click(object sender, EventArgs e)
{
DeleteItems(GetSelectedItems(selectionversion));
}
bool
dockmanualset;
[Category(NavigatorCategory)]
public
DockStyle NavigatorLocation
{
get
{
if (!ShowNavigator) return DockStyle.Left;
return
navigator.Dock;
}
set

{
ShowNavigator = value != DockStyle.None;
if
(!ShowNavigator) return;
if
(value == DockStyle.Fill)
value = GetDefaultNavigatorDock();
navigator.Dock = value;
dockmanualset = ShouldSerializeNavigatorLocation();
}
}
bool
ShouldSerializeNavigatorLocation()
{
return navigator != null && NavigatorLocation != GetDefaultNavigatorDock();
}

#endregion
#region Delete button
[DefaultValue(false)]
[Category(ItemCategory)]
public
bool ShowDeleteButtonPerItem
{
get { return deletebuttonicon != null; }
set

{
if (ShowDeleteButtonPerItem == value) return;
if
(value)
DeleteButtonIcon = GetDeleteButtonImage();
else
DeleteButtonIcon = null;
InvalidateMainPanel();
}
}
void InvalidateMainPanel()
{
#if IncludeDesignTimeSupport
if (DesignMode)
{
SetDesignTimeInstanceSize();
mainpanel.Invalidate();
}
else
#endif
if (!IsInitializing)
{
mainpanel.SetSize();
SetInstanceLocation();
mainpanel.Invalidate();
}
}
private Icon deletebuttonicon;
[Category(ItemCategory)]
public
Icon DeleteButtonIcon
{
get { return deletebuttonicon; }
set

{
deletebuttonicon = value;
mainpanel.Invalidate();
}
}
bool
ShouldSerializeDeleteButtonIcon()
{
return deletebuttonicon != null && deletebuttonicon != DefaultDeleteButtonImage;
}
public static Icon DefaultDeleteButtonImage;
static Icon GetDeleteButtonImage()
{
if (DefaultDeleteButtonImage == null)
DefaultDeleteButtonImage = GetIcon(131);
return DefaultDeleteButtonImage;
}
const int DeleteButtonWidth = 16;
Rectangle
GetDeleteButtonBounds(int Index)
{
Rectangle res = new Rectangle(
GetItemLeft(Index) + ItemSize.Width + itemPadding.Left + 1,
GetItemTop(Index) + itemPadding.Top,
DeleteButtonWidth, DeleteButtonWidth);
if (lefttoright && DrawInsertButtonsBetweenItems)
res.X += InsertButtonHeight;
return res;
}
#endregion
#region Insert buttons
public enum InsertButtonStyles
{
None,
BeginOnly = 1,
EndOnly = 2,
AfterEachItem = 4 | EndOnly,
BeforeEachItem = 8 | BeginOnly,
BeforeAndAfterEachItem = AfterEachItem | BeforeEachItem
}
private InsertButtonStyles insertbuttonstyle;
[DefaultValue(InsertButtonStyles.None)]
[Category(ItemCategory)]
public
InsertButtonStyles InsertButtonStyle
{
get { return insertbuttonstyle; }
set

{
if (insertbuttonstyle == value) return;
insertbuttonstyle = value;
ShowInsertButtons = value != InsertButtonStyles.None;
InvalidateMainPanel();
}
}
bool DrawInsertButtonsBetweenItems
{
get
{
return ShowInsertButtons && (insertbuttonstyle & InsertButtonStyles.BeforeAndAfterEachItem) > InsertButtonStyles.EndOnly;
}
}
bool DrawStartInsertButton
{
get
{
return ShowInsertButtons && (insertbuttonstyle & InsertButtonStyles.BeginOnly) > 0;
}
}
bool DrawEndInsertButton
{
get
{
return (insertbuttonstyle & InsertButtonStyles.EndOnly) > 0;
}
}
public class InsertItemEventArgs : HandledEventArgs
{
public readonly int Index;
public
InsertItemEventArgs(int Index)
{
this.Index = Index;
}
}
void OnInsertItem(int Index)
{
OnInsertItem(new InsertItemEventArgs(Index));
}
public event EventHandler<InsertItemEventArgs> InsertItem;
protected virtual void OnInsertItem(