OK, this is probably overkill - but I got bored on the train ;-p
Basically, to minimise bloat, I've wrapped a List<T> to linearize an
arary - i.e. an array [3, *] would be a list where the first 3 values
are row 0, the next 3 are row 1, etc.
Then 'cos I was still bored, I wrapped this with a custom view so that
you can throw it at a grid (you can edit cells, but not add rows) or
single binding - and added change noification.
It should do most what you need!!!!
Marc
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
class Program
{
static void Main()
{
Matrix<int> grid = new
Matrix<int>(MatrixOrientation.FixedWidth, 10, 5);
grid[2, 2] = 5;
grid.Add(new int[] {1,2,3,4,5,6,7,8,9,0});
Application.EnableVisualStyles();
Application.Run(new Form
{
Controls = {
// demo list (multi) binding
new DataGridView {
Dock = DockStyle.Fill,
DataSource = grid
},
// demo list (single) binding
new TextBox {
Dock = DockStyle.Bottom,
DataBindings = { {"Text", grid, "Col3"}}
}
},
// demo single row binding
DataBindings = {{"Text", grid.DefaultView[2], "Col2"}}
});
}
}
public static class ClassExt
{
public static void CheckNull<T>(this T value, string paramName)
where T : class
{
if (value == null) throw new ArgumentNullException(paramName);
}
}
public enum MatrixOrientation
{
FixedWidth, FixedHeight
}
public class MatrixChangedEventArgs : EventArgs
{
private readonly int x, y;
public int X { get { return x; } }
public int Y { get { return y; } }
public MatrixChangedEventArgs(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Matrix<T> : IListSource
{
public override string ToString()
{
return string.Format("{0}[{1},{2}]", typeof(T).Name, Width,
Height);
}
public event EventHandler<MatrixChangedEventArgs> ValueChanged;
protected void OnValueChanged(int x, int y)
{
if (ValueChanged != null)
{
ValueChanged(this, new MatrixChangedEventArgs(x, y));
}
}
private readonly int fixedSize;
private readonly MatrixOrientation orientation;
public MatrixOrientation Orientation { get { return orientation; } }
private readonly List<T> list;
public Matrix(MatrixOrientation orientation, int width, int height)
{
switch (orientation)
{
case MatrixOrientation.FixedHeight:
fixedSize = height;
break;
case MatrixOrientation.FixedWidth:
fixedSize = width;
break;
default:
throw new ArgumentOutOfRangeException("orientation");
}
this.orientation = orientation;
CheckDimension(width, orientation ==
MatrixOrientation.FixedWidth, "width");
CheckDimension(height, orientation ==
MatrixOrientation.FixedWidth, "height");
list = new List<T>(width * height);
AddRange(orientation == MatrixOrientation.FixedWidth ? height :
width);
}
public void TrimExcess()
{
list.TrimExcess();
}
private void CheckDimension(int size, bool disallowEqual, string
paramName)
{
if (size < 0) throw new ArgumentOutOfRangeException(paramName,
"Dimension cannot be negative");
if (size == 0 && disallowEqual) throw new
ArgumentOutOfRangeException(paramName, "Dimension cannot be zero");
}
public Matrix(MatrixOrientation orientation, int fixedSize)
: this(orientation,
orientation == MatrixOrientation.FixedWidth ? fixedSize : 0,
orientation == MatrixOrientation.FixedHeight ? fixedSize : 0)
{
}
public void Clear()
{
list.Clear();
}
private int Linearize(int x, int y)
{
if (orientation == MatrixOrientation.FixedWidth)
{
if (x < 0 || y < 0 || x >= fixedSize) throw new
ArgumentOutOfRangeException();
return (y * fixedSize) + x;
}
else
{
if (y < 0 || y < 0 || y >= fixedSize) throw new
ArgumentOutOfRangeException();
return (x * fixedSize) + y;
}
}
public T this[int x, int y]
{
get
{
return list[Linearize(x, y)];
}
set
{
list[Linearize(x, y)] = value;
OnValueChanged(x, y);
}
}
public void Add() { AddRange(1); }
public void Add(T[] values)
{
values.CheckNull("values");
if (values.Length != fixedSize) throw new
ArgumentException("Array must be the same size as the fixed dimension",
"values");
list.AddRange(values);
}
public void AddRange(int count) {
CheckDimension(count, false, "count");
count *= fixedSize;
while (count-- > 0)
{
list.Add(default(T));
}
}
public void Remove() { RemoveRange(1); }
public void RemoveRange(int count)
{
CheckDimension(count, false, "count");
count *= fixedSize;
list.RemoveRange(list.Count - count, count);
}
public int FixedSize { get { return fixedSize; } }
public int DynamicSize { get { return list.Count / fixedSize; } }
public int Width
{
get
{
return orientation == MatrixOrientation.FixedWidth
? FixedSize : DynamicSize;
}
}
public int Height
{
get
{
return orientation == MatrixOrientation.FixedHeight
? FixedSize : DynamicSize;
}
}
#region IListSource Members
bool IListSource.ContainsListCollection
{
get { return false; }
}
System.Collections.IList IListSource.GetList()
{
return DefaultView;
}
#endregion
private MatrixView<T> defaultView;
public MatrixView<T> DefaultView {
get {
if(defaultView == null) {
defaultView = new MatrixView<T>(this, false);
}
return defaultView;
}
}
}
public class MatrixView<T> : ITypedList, IList<MatrixViewRow<T>>, IList,
IBindingList
{
private readonly Matrix<T> parent;
private readonly bool transposed;
public MatrixView<T> Transpose()
{
return new MatrixView<T>(parent, !transposed);
}
internal MatrixView(Matrix<T> parent, bool transposed)
{
parent.CheckNull("parent");
this.parent = parent;
this.transposed = transposed;
}
public PropertyDescriptorCollection GetColumns()
{
int width = Width;
List<PropertyDescriptor> props = new
List<PropertyDescriptor>(width);
for (int i = 0; i < width; i++)
{
props.Add(new MatrixViewCol<T>(this, i));
}
return new PropertyDescriptorCollection(props.ToArray(), true);
}
PropertyDescriptorCollection
ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
if (listAccessors != null && listAccessors.Length != 0)
{
throw new NotSupportedException();
}
return GetColumns();
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
return "";
}
public void Clear()
{
parent.Clear();
}
public int IndexOf(MatrixViewRow<T> slice)
{
slice.CheckNull("slice");
return slice.Index;
}
void IList<MatrixViewRow<T>>.Insert(int index, MatrixViewRow<T> slice)
{
throw new NotSupportedException();
}
void IList<MatrixViewRow<T>>.RemoveAt(int index)
{
throw new NotSupportedException();
}
bool ICollection<MatrixViewRow<T>>.Contains(MatrixViewRow<T> slice)
{
if (slice == null) return false;
return slice.Index >= 0 && slice.Index < Height;
}
public int Width
{
get { return transposed ? parent.Height : parent.Width; }
}
public int Height
{
get { return transposed ? parent.Width: parent.Height; }
}
public MatrixViewRow<T> this[int index]
{
get { return new MatrixViewRow<T>(this, index); }
set { throw new NotSupportedException(); }
}
void ICollection<MatrixViewRow<T>>.Add(MatrixViewRow<T> slice)
{
throw new NotSupportedException();
}
bool ICollection<MatrixViewRow<T>>.Remove(MatrixViewRow<T> slice)
{
throw new NotSupportedException();
}
public int Count { get { return Height; } }
bool ICollection<MatrixViewRow<T>>.IsReadOnly { get { return false; } }
bool IList.IsReadOnly { get { return ThisList.IsReadOnly; } }
bool IList.IsFixedSize { get { return true; } }
public IEnumerator<MatrixViewRow<T>> GetEnumerator()
{
for (int i = 0; i < Height; i++)
{
yield return this;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
int IList.Add(object obj)
{
MatrixViewRow<T> slice = (MatrixViewRow<T>)obj;
ThisList.Add(slice);
return slice.Index;
}
bool IList.Contains(object obj)
{
return ThisList.Contains((MatrixViewRow<T>)obj);
}
int IList.IndexOf(object obj)
{
return IndexOf((MatrixViewRow<T>)obj);
}
void IList.Remove(object obj)
{
ThisList.Remove((MatrixViewRow<T>)obj);
}
private IList<MatrixViewRow<T>> ThisList { get { return this; } }
void IList.Insert(int index, object obj)
{
ThisList.Insert(index, (MatrixViewRow<T>)obj);
}
void IList.RemoveAt(int index)
{
ThisList.RemoveAt(index);
}
object IList.this[int index]
{
get { return this[index]; }
set { this[index] = (MatrixViewRow<T>)value; }
}
object ICollection.SyncRoot
{
get { return parent; }
}
bool ICollection.IsSynchronized
{
get { return false; }
}
void ICollection.CopyTo(Array values, int index)
{
for (int i = 0; i < Height; i++)
{
values.SetValue(this, i + index);
}
}
void ICollection<MatrixViewRow<T>>.CopyTo(MatrixViewRow<T>[]
values, int index)
{
for (int i = 0; i < Height; i++)
{
values[i + index] = this;
}
}
public bool IsTransposed { get { return transposed; } }
public string RowName(int index)
{
return (IsTransposed ? "Col" : "Row") + index.ToString();
}
public string ColName(int index)
{
return (IsTransposed ? "Row" : "Col") + index.ToString();
}
public T this[int colIndex, int rowIndex]
{
get { return IsTransposed ? parent[rowIndex, colIndex] :
parent[colIndex, rowIndex]; }
set
{
if (IsTransposed)
{
parent[rowIndex, colIndex] = value;
}
else
{
parent[colIndex, rowIndex] = value;
}
}
}
bool IBindingList.AllowNew { get { return false; } }
bool IBindingList.AllowRemove { get { return false; } }
bool IBindingList.AllowEdit { get { return true; } }
object IBindingList.AddNew()
{
throw new NotSupportedException();
}
void IBindingList.AddIndex(PropertyDescriptor property) { }
void IBindingList.RemoveIndex(PropertyDescriptor property) { }
bool IBindingList.SupportsSorting { get { return false; } }
void IBindingList.ApplySort(PropertyDescriptor property,
ListSortDirection direction)
{
throw new NotSupportedException();
}
PropertyDescriptor IBindingList.SortProperty { get { return null; } }
ListSortDirection IBindingList.SortDirection { get { return
ListSortDirection.Ascending; } }
bool IBindingList.IsSorted { get { return false; } }
void IBindingList.RemoveSort() { }
bool IBindingList.SupportsSearching { get { return false; } }
int IBindingList.Find(PropertyDescriptor property, object obj)
{
throw new NotSupportedException();
}
bool IBindingList.SupportsChangeNotification { get { return true; } }
private ListChangedEventHandler listChanged;
public event ListChangedEventHandler ListChanged
{
add
{
bool first = listChanged == null;
listChanged += value;
if (first && listChanged != null) parent.ValueChanged +=
ListChangedHandler;
}
remove {
listChanged -= value;
if (listChanged == null) parent.ValueChanged -=
ListChangedHandler;
}
}
private void OnListChanged(int index) {
if(listChanged != null) {
listChanged(this, new
ListChangedEventArgs(ListChangedType.ItemChanged, index));
}
}
private void OnRowChanged(int index) {
EventHandler handler;
if (rowHandlers != null && rowHandlers.TryGetValue(index, out
handler))
{
handler(this, EventArgs.Empty);
}
}
private void ListChangedHandler(object sender,
MatrixChangedEventArgs args)
{
int index = IsTransposed ? args.X : args.Y;
OnListChanged(index);
}
private void RowChangedHandler(object sender,
MatrixChangedEventArgs args)
{
int index = IsTransposed ? args.X : args.Y;
OnRowChanged(index);
}
internal void AddRowHandler(int index, EventHandler handler)
{
if (handler == null) return;
if (rowHandlers == null)
{
rowHandlers = new Dictionary<int, EventHandler>();
parent.ValueChanged += RowChangedHandler;
}
EventHandler value;
rowHandlers.TryGetValue(index, out value);
value += handler;
rowHandlers[index] = value;
}
internal void RemoveRowHandler(int index, EventHandler handler)
{
if (handler == null || rowHandlers == null) return;
EventHandler value;
if (rowHandlers.TryGetValue(index, out value))
{
value -= handler;
if (value == null)
{
rowHandlers.Remove(index);
if (rowHandlers.Count == 0)
{
rowHandlers = null;
parent.ValueChanged -= RowChangedHandler;
}
}
else
{
rowHandlers[index] = value;
}
}
}
private Dictionary<int, EventHandler> rowHandlers;
}
public class MatrixViewRow<T> : ICustomTypeDescriptor
{
private readonly int index;
public int Index { get { return index; } }
private readonly MatrixView<T> parent;
internal MatrixViewRow(MatrixView<T> parent, int index)
{
if (index < 0) throw new ArgumentOutOfRangeException("index");
parent.CheckNull("parent");
this.index = index;
this.parent = parent;
}
public override string ToString()
{
return parent.RowName(index);
}
#region ICustomTypeDescriptor Members
private static Type RowType { get { return
typeof(MatrixViewRow<T>); } }
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(RowType);
}
string ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(RowType);
}
string ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(RowType);
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(RowType);
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(RowType);
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(RowType);
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(RowType, editorBaseType);
}
EventDescriptorCollection
ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(RowType, attributes);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(RowType);
}
PropertyDescriptorCollection
ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
return ThisDesc.GetProperties();
}
private ICustomTypeDescriptor ThisDesc { get { return this; } }
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return parent.GetColumns();
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
}
internal class MatrixViewCol<T> : PropertyDescriptor
{
public override string ToString()
{
return Name;
}
private static readonly Attribute[] fixedAttribs = new Attribute[0];
private readonly MatrixView<T> parent;
private int index;
public MatrixViewCol(MatrixView<T> parent, int index) : base(parent
== null ? "None" : parent.ColName(index), fixedAttribs)
{
parent.CheckNull("parent");
this.parent = parent;
this.index = index;
}
public override Type ComponentType
{
get { return typeof(MatrixViewRow<T>); }
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(T); }
}
public override bool CanResetValue(object component)
{
return true;
}
public override void ResetValue(object component)
{
SetValue(component, default(T));
}
private MatrixViewRow<T> Row(object component)
{
return ((MatrixViewRow<T>)component);
}
public override void SetValue(object component, object value)
{
parent[index, Row(component).Index] = (T)value;
}
public override object GetValue(object component)
{
return parent[index, Row(component).Index];
}
public override bool ShouldSerializeValue(object component)
{
return
!EqualityComparer<T>.Default.Equals((T)GetValue(component), default(T));
}
protected override object GetInvocationTarget(Type type, object
instance)
{
return base.GetInvocationTarget(type, instance);
}
public override bool SupportsChangeEvents
{
get { return true; }
}
public override void AddValueChanged(object component, EventHandler
handler)
{
parent.AddRowHandler(Row(component).Index, handler);
}
public override void RemoveValueChanged(object component,
EventHandler handler)
{
parent.RemoveRowHandler(Row(component).Index, handler);
}
}