IList implementation as data source for DataGridView

T

t.mall01

Hi,

I'm implementing a custom biz object list for use as a DataGridView
data source.

Is there a way to implement ListChangedEventHandler similar to how it
is used by a BindingList<T> object without actually using
BindingList<T>?

I am instead attempting to implement said event handler through the
IBindingList interface, but it does not capture a cell edit in the
DGV.

Here's the relevant code from my class:

public class DataObjectList<T> : IList , IBindingList, ITypedList,
ICancelAddNew, IRaiseItemChangedEvents where T : class, new()
{
private ListChangedEventArgs resetEvent = new
ListChangedEventArgs(ListChangedType.Reset, -1);
private ListChangedEventHandler onListChanged;

protected virtual void OnListChanged(ListChangedEventArgs ev)
{
if (onListChanged != null)
{
onListChanged(this, ev);
}
}

event ListChangedEventHandler IBindingList.ListChanged
{
add
{
onListChanged += value;
}
remove
{
onListChanged -= value;
}
}

The biz objects I use implement INotifyPropertyChanged and
IEditableObject. The underlying list that holds the objects is a
List<T> object.

Is there something I am overlooking or not including in my list class
that is not allowing the DGV to raise the ListChanged event when the
user edits a cell?

Thanks in advance for any help or suggestions,

TMall
 
M

Marc Gravell

Is there something I am overlooking or not including in my list class
that is not allowing the DGV to raise the ListChanged event when the
user edits a cell?

Other way around; the list raises the ListChanged event to notify the
DGV that something has changed *it* (there's a joke involving Soviet
Russia in there somewhere...). It is the lists job to monitor the
individual items and tell the DGV about changes; so your list would
simply have to do a lot of subscribing...

Marc
 
T

t.mall01

I should have added the the IBindingList.AllowEdit and
IBindingList.SupportsChangeNotification properties both return true.

And I am able to capture a new rows throught the IBindingList.AddNew,
and row removals through the IList.RemoveAt methods; it's just the
updating of an individual row that is not being caught. The changes
appear in my list, so the binding is working, but I also want to catch
the event as it happens.

Thanks, TMall
 
T

t.mall01

Other way around; the list raises the ListChanged event to notify the
DGV that something has changed *it* (there's a joke involving Soviet
Russia in there somewhere...). It is the lists job to monitor the
individual items and tell the DGV about changes; so your list would
simply have to do a lot of subscribing...

Marc

I guess I was thinking the ListChangedEventHandler was also
used by the grid somehow to notify the list.

What events would this list need to subscribe to? Would this require a
custom
grid control to fire custom events?

BTW, I'm reading the book on databinding by Brian Noyes, which is
basically the
bible that everyone seems to reference, and I think you have reference
in one of your
posts. I'm at the chapters that deal with this very thing, and Noyes
recommends
the BindingList<T>, but I've already created this monster, and it's
nearly done. All appears
to be kosher with exception for this update event catching.

TMall
 
T

t.mall01

I have searched and searched for this answer, and either haven't found
it, or overlooked it, but I don't know how I would have missed it.
I'm guessing it's some sort of secret, holy grail that once you find
it, you never tell.

Anyone care to enlighten one who has truly searched for this?

Or maybe I'm using the wrong search words, or haven't stumbled on the
right .Net site.

I seriously thought the IBindingList.ListChangedEventHandler would be
raised by the DGV's event as well; no article or reading I've come
across would prove otherwise, although nothing has stated that it
would either.

Again, any help would be greatly appreciated, TMall
 
M

Marc Gravell

What events would this list need to subscribe to?
Whichever event is interesting ;-p

This could be INotifyPropertyChanged, allowing you to capture all
properties in a single hook - but worst case you would use the
PropertyDescriptors and subscribe to each individual property. And
either way, do that for every row in the grid. And unsubscribe as
things are removed. And raise the necessary insert etc as you add
items. The PropertyDescriptor approach automatically covers the
{Foo}Changed pattern.

I've written a full IBindingList/IBindingListView implementation, and
quite simply it is too long to post ;-p
Would this require a custom grid control to fire custom events?
No; the grid is the consumer; the list is the producer; it is the list
that must be written.
and I think you have reference in one of your posts.
Not me; never read it ;-p

Marc
 
M

Marc Gravell

every row in the grid

I meant every item in the list; subtle difference, but important...

Marc
 
M

Marc Gravell

was also used by the grid somehow to notify the list.
No; the grid simply updates the item via the PropertyDescriptor(s); if
the list cares about updates, then the list should be listening to
events on the items.

Generally this notificatin support is only necessary for full
"observer" implementations; if the grid is the only thing editing the
list then it doesn't need this - as the grid *knows* a change has
happened, 'cos it did it.

Marc
 
T

t.mall01

Whichever event is interesting ;-p

This could be INotifyPropertyChanged, allowing you to capture all
properties in a single hook - but worst case you would use the
PropertyDescriptors and subscribe to each individual property. And
either way, do that for every row in the grid. And unsubscribe as
things are removed. And raise the necessary insert etc as you add
items. The PropertyDescriptor approach automatically covers the
{Foo}Changed pattern.

I've written a full IBindingList/IBindingListView implementation, and
quite simply it is too long to post ;-p


No; the grid is the consumer; the list is the producer; it is the list
that must be written.


Not me; never read it ;-p

Marc

What I'm trying to do is to capture the basic CRUD events, track the
events through 3 lists (add, delete, update), and then when the user
is done with the editing, transact one batch update to the database
stepping through the lists of object references.

I'm doing it this way as opposed to storing a complete copy of the
original data (or re-pulling it) and then doing a comparison before
the batch update, similar to how the DataAdapter and DataTable and
DataSet bloat objects work.

The add event is pretty simply tracked through the
IBindingList.AddNew; for deletion, the grid calls IList.RemoveAt to
remove a row, and I actually ran into a problem with that as well,
because when I call the underlying dataList<T>.RemoveAt, it causes the
CurrencyManager to again call the IList.RemoveAt, so that's another
thing I've yet to deal with.

For updating, you mention PropertyDescriptors; are you saying the
answer lies in the ITypedList interface?
 
M

Marc Gravell

For updating, you mention PropertyDescriptors; are you saying the
answer lies in the ITypedList interface?- Hide quoted text -
Absolutely not; ITypedList is only useful before List<T> and
TypeDescriptionProvider came about. Don't go that route; it is really,
really messy (you assume a lot more responsibility than you might
expect for navigating sub-properties).

PropertyDescriptor is simply the abstract way of talking about
properties; almost all UI binding (such as grids) talks via
PropertyDescriptor; but you don't need to worry about "how" so long as
your list has a typed "public T this[int index]" indexer, which
List<T> etc does.


I know what you are doing; my implementation does the self-same thing
(track the original state and new state).
for deletion, the grid calls IList.RemoveAt to
remove a row, and I actually ran into a problem with that as well,
because when I call the underlying dataList<T>.RemoveAt, it causes the
CurrencyManager to again call the IList.RemoveAt
Not sure I understand that; the inner list(s) should be pretty-much
hidden...?

Marc
 
T

t.mall01

For updating, you mention PropertyDescriptors; are you saying the
answer lies in the ITypedList interface?- Hide quoted text -

Absolutely not; ITypedList is only useful before List<T> and
TypeDescriptionProvider came about. Don't go that route; it is really,
really messy (you assume a lot more responsibility than you might
expect for navigating sub-properties).

PropertyDescriptor is simply the abstract way of talking about
properties; almost all UI binding (such as grids) talks via
PropertyDescriptor; but you don't need to worry about "how" so long as
your list has a typed "public T this[int index]" indexer, which
List<T> etc does.

I know what you are doing; my implementation does the self-same thing
(track the original state and new state).
for deletion, the grid calls IList.RemoveAt to
remove a row, and I actually ran into a problem with that as well,
because when I call the underlying dataList<T>.RemoveAt, it causes the
CurrencyManager to again call the IList.RemoveAt

Not sure I understand that; the inner list(s) should be pretty-much
hidden...?

Marc

Here's what my RemoveAt looks like:

public void RemoveAt(int index)
{
T item = dataList[index];
MarkDeleted(item);
dataList.RemoveAt(index);
}

If I comment out the last line that actually does the delete, the grid
does nothing with the highlighted row after the Delete button is
pressed. With this line active, the RemoveAt function is called
twice, not sure why.

You mention you've done something similar; have you published this
anywhere, and/or is it available for download, or maybe purchase?
 
M

Marc Gravell

If I comment out the last line that actually does the delete, the grid
does nothing with the highlighted row after the Delete button is
pressed.
Did you tell it? In particular, by raising ItemDeleted - but on *your* list;
the outside world should never see the inner list; my code for this method
(well, the private method that sits behind the various Remove, RemoveAt
etc):

private void InnerRemove(int index) {
// <snip> some things you don't need
Items.RemoveAt(index);
OnListChanged(new
ListChangedEventArgs(ListChangedType.ItemDeleted, index));
// <snip> some other things you don't need
}

Where "Items" is my private (well, protected in my case) inner list.
You mention you've done something similar; have you published this
anywhere, and/or is it available for download, or maybe purchase?
Nope
 
T

t.mills624

Absolutely not; ITypedList is only useful before List<T> and
TypeDescriptionProvider came about. Don't go that route; it is really,
really messy (you assume a lot more responsibility than you might
expect for navigating sub-properties).
PropertyDescriptor is simply the abstract way of talking about
properties; almost all UI binding (such as grids) talks via
PropertyDescriptor; but you don't need to worry about "how" so long as
your list has a typed "public T this[int index]" indexer, which
List<T> etc does.
I know what you are doing; my implementation does the self-same thing
(track the original state and new state).
Not sure I understand that; the inner list(s) should be pretty-much
hidden...?

Here's what my RemoveAt looks like:

  public void RemoveAt(int index)
    {
        T item = dataList[index];
        MarkDeleted(item);
        dataList.RemoveAt(index);
    }

If I comment out the last line that actually does the delete, the grid
does nothing with the highlighted row after the Delete button is
pressed.  With this line active, the RemoveAt function is called
twice, not sure why.

You mention you've done something similar; have you published this
anywhere, and/or is it available for download, or maybe purchase?- Hide quoted text -

- Show quoted text -

Does this "implementation" completely missing the mark on how it's
supposed to be done? From the Call stack entries, the CurrencyManager
is trying to remove the row again.
 
T

t.mills624

Did you tell it? In particular, by raising ItemDeleted - but on *your* list;
the outside world should never see the inner list; my code for this method
(well, the private method that sits behind the various Remove, RemoveAt
etc):

        private void InnerRemove(int index) {
            // <snip> some things you don't need
            Items.RemoveAt(index);
            OnListChanged(new
ListChangedEventArgs(ListChangedType.ItemDeleted, index));
            // <snip> some other things you don't need
        }

Where "Items" is my private (well, protected in my case) inner list.


Nope

Adding the OnListChanged call after the internal RemoveAt fixed the
problem of it trying to delete again. So before, the underlying data
list was being modified, and when the CurrencyManager detected this,
it tried to execute the public RemoveAt again because it wasn't
officially aware of the change; the call to the OnListChanged event
makes it aware now.

There are some fundamental things that I'm overlooking, mostly because
I've started from a point of borrowing other people's code and trying
to modify it after the fact, without knowing the original intent or
purpose.

Although this code is also borrowed, it comes from Noye's book, and
there is some explanation with it, although I haven't quite figured it
all out:

PropertyDescriptorCollection propDescs =
TypeDescriptor.GetProperties(item);
foreach (PropertyDescriptor propDesc in propDescs)
{
if (propDesc.SupportsChangeEvents)
{
propDesc.RemoveValueChanged(item, OnItemChanged);
}
}

void OnItemChanged(object sender, EventArgs args)
{
int index = dataList.IndexOf((T)sender);
OnListChanged(new
ListChangedEventArgs(ListChangedType.ItemChanged, index));
}

bool IRaiseItemChangedEvents.RaisesItemChangedEvents
{
get { return true; }
}

I do follow that, for properties that are changed outside the grid, it
raises the ListChanged event when an OnItemChanged event occurs, but
this was for changes outside the grid. Are you saying that this code
could be adapted to apply to row edits within the grid, am I following
you correctly?
 
M

Marc Gravell

[PropertyDescriptor code]
This is what I was mentioning earlier; if your objects support
INotifyPropertyChanged, then a simpler (and more efficient) approach
is to use this hook rather than binding to each individual property.
In this case, this whole loop becomes:
item.PropertyChanged -= YourItemPropertyChangedHandler;

likewise, you need a matching "add" hook:
item.PropertyChanged += YourItemPropertyChangedHandler;

Which is far more appealing than looking at each property ;-p

Of course, you need to catch a PropretyChangedEventArgs in the handler
(not just an EventArgs), but other than that it should stay more or
less the same: find the index of whatever raised the event, and tell
the listeners (grid) that this row has (somehow, we don't care how)
been edited.
 Are you saying that this code
could be adapted to apply to row edits within the grid, am I following
you correctly?
Row edits within the grid should just work; are you having a problem
there?
 
T

t.mills624

[PropertyDescriptor code]

This is what I was mentioning earlier; if your objects support
INotifyPropertyChanged, then a simpler (and more efficient) approach
is to use this hook rather than binding to each individual property.
In this case, this whole loop becomes:
item.PropertyChanged -= YourItemPropertyChangedHandler;

likewise, you need a matching "add" hook:
item.PropertyChanged += YourItemPropertyChangedHandler;

Which is far more appealing than looking at each property ;-p

Of course, you need to catch a PropretyChangedEventArgs in the handler
(not just an EventArgs), but other than that it should stay more or
less the same: find the index of whatever raised the event, and tell
the listeners (grid) that this row has (somehow, we don't care how)
been edited.
 Are you saying that this code
could be adapted to apply to row edits within the grid, am I following
you correctly?

Row edits within the grid should just work; are you having a problem
there?

Thanks for pointing me in the right direction there, or you may have
shared more than just
a share, I'm not sure which because I'm not completely sure how the
code should look. But thanks for the re-assurance about he
INotifyPropertyChange interface, as my objects do implement that:

public class TTasks : IEditableObject, INotifyPropertyChanged
{

#region INotify

public DateTime LastUpdate
{
get
{
return lastUpdate;
}
}

protected void NotifyPropertyChanged(string propertyName)
{
if (propertyName != "LastUpdate")
{
lastUpdate = DateTime.Now;
NotifyPropertyChanged("LastUpdate");
}

if (PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs(propertyName));
}
#endregion
}

The LastUpdate property is something I lifted off another example, and
it might come in handy.

What's still fuzzy to me is how Events and Delegates work together; I
know there's some good
examples even in MSDN, but the concept for some reason is not the
easiest for me to pick up on.
Like how to hook into each objects PropertyChanged event from the List
object, I'm not sure how to wire
that up, is that through the PropertyDescriptor as well?

And the edits made in the grid row work fine and get populated to the
underlying list, it's catching the update event itself that I'm trying
to do.

Thanks again; I'm upgrading some VB6 projects, and originally started
doing it in .Net 1.1, then after bumbling around with the databinding
nightmare in that version, decided to leap ahead to 2.0, especially
since 3.5 is now the latest and greatest. WPF and LINQ look promising
- have you started into those Marc?
 
M

Marc Gravell

Your NotifyPropertyChanged looks fine. There is a convention to name
such
"On{eventName}", so I might call this OnPropertyChanged, but it won't
make and real difference.
What's still fuzzy to me is how Events and Delegates work together
If you understand what a delegate is (comparable to a "function
pointer" if that
means anything), then an event is just a way of limiting extenal
access to a delegate;
in the same way that a property provides safe "get" and "set" access
to a field, *all*
an event does is provide safe "add" (subscribe) and
"remove" (unsubscribe) access to
a delegate.

i.e.
public event EventHandler Foo;
is identical (ish) to:
private EventHandler foo; // private field
public event EventHandler Foo {
add {foo+=value;}
remove {foo-=value;}
}
This stops the caller from:
* accidentally wiping out other subscribers by doing "obj.Foo = null"
instead of "obj.Foo -= SomeHandler"
* invoking the delegate themselves (obj.Foo(...);)
* snooping at the other subscribers
* other forms of abuse ;-p
Like how to hook into each objects PropertyChanged event from the List
object, I'm not sure how to wire that up, is that through the
PropertyDescriptor as well?
No; PropertyDescriptor here would be necessary only if you *didn't*
have
INotifyPropertyChanged; I will try to come up with an eample on the
train...
WPF and LINQ look promising - have you started into those Marc?
LINQ yes - quite a bit in fact; the WPF, not so much. I'd love to get
into WPF, but I haven't found the time.
 
M

Marc Gravell

OK; here is a pretty-bare-bones implementation that supports change
notification of items via INotifyPropertyChanged...

You might also consider adding support for ICancelAddNew, but this interface
is pretty backwards in my opinion - I suspect it was designed to fit how
DataTable worked at the time, rather than "how should this be done?" - and
it is *not* as trivial as removing the row...
Note that if you provide a proper ApplySort implemention then users will be
able to sort via the column headers; I've posted examples of this
previously.

I haven't added many comments due to time; but please ask any questions you
like. The main thing to note is how anything adding/removing/replacing items
in the list uses WatchForChange to subscribe/unsubscribe to the
PropertyChanged event.

Anyways... [long code warning]

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

class Bar : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void UpdateField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return;
field = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(propertyName));
}
}
private string name;
private DateTime dateOfBirth;
public string Name
{
get { return name; }
set { UpdateField(ref name, value, "Name"); }
}
[DisplayName("Date of Birth")]
public DateTime DateOfBirth
{
get { return dateOfBirth; }
set { UpdateField(ref dateOfBirth, value, "DateOfBirth"); }
}
}
class Program
{
[STAThread]
static void Main()
{
FooList<Bar> data = new FooList<Bar>();
Random rand = new Random();
Application.EnableVisualStyles();
using(Form form = new Form())
using(DataGridView view = new DataGridView())
using(Button btn1 = new Button())
using(Button btn2 = new Button())
using (Button btn3 = new Button())
using (Button btn4 = new Button())
{
view.Dock = DockStyle.Fill;
btn1.Dock = btn2.Dock = btn3.Dock = btn4.Dock =
DockStyle.Bottom;
view.DataSource = data;
form.Controls.AddRange(new Control[] { view, btn1, btn2, btn3,
btn4 });
btn1.Text = "Clear";
btn1.Click += delegate { data.Clear(); };
btn2.Text = "Insert";
btn2.Click += delegate {
Bar b = new Bar();
b.Name = "New bar";
int index = data.Count == 0 ? 0 : rand.Next(0, data.Count);
data.Insert(index, b);
};
btn3.Text = "Remove";
btn3.Click += delegate
{
if (data.Count > 0)
{
data.RemoveAt(rand.Next(0, data.Count));
}
};
btn4.Text = "Update";
btn4.Click += delegate
{
if (data.Count > 0)
{
data[rand.Next(0, data.Count)].Name += "*";
}
};
Application.Run(form);
}
}
}

class FooList<T> : IList<T>, IBindingList, IRaiseItemChangedEvents
{
object ICollection.SyncRoot { get { return
((ICollection)InnerList).SyncRoot; } }
bool ICollection.IsSynchronized { get { return
((ICollection)InnerList).IsSynchronized; } }
public int IndexOf(T item) { return InnerList.IndexOf(item); }
int IList.IndexOf(object obj) { return IndexOf((T)obj); }
bool IList.Contains(object obj) { return Contains((T)obj); }
void IList.Remove(object obj) { Remove((T)obj); }
public bool Contains(T item) {return InnerList.Contains(item);}
public void CopyTo(T[] array, int arrayIndex) { InnerList.CopyTo(array,
arrayIndex); }
void ICollection.CopyTo(Array array, int arrayIndex) {
((ICollection)InnerList).CopyTo(array, arrayIndex); }
public int Count { get { return InnerList.Count; } }
void IList.Insert(int index, object obj) { Insert(index, (T)obj); }
bool ICollection<T>.IsReadOnly { get { return false; } }
bool IList.IsReadOnly { get { return false; } }
bool IBindingList.AllowNew { get { return true; } }
bool IList.IsFixedSize { get { return false; } }
bool IBindingList.AllowEdit { get { return true; } }
bool IBindingList.AllowRemove { get { return true; } }
public IEnumerator<T> GetEnumerator() { return
InnerList.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
void IBindingList.AddIndex(PropertyDescriptor prop) { }
void IBindingList.RemoveIndex(PropertyDescriptor prop) { }
bool IBindingList.SupportsSorting { get { return false; } }
bool IBindingList.IsSorted { get { return false; } }
void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection
dir) { throw new NotSupportedException(); }
void IBindingList.RemoveSort() { }
PropertyDescriptor IBindingList.SortProperty { get { return null; } }
ListSortDirection IBindingList.SortDirection { get { return
ListSortDirection.Ascending; } }
bool IBindingList.SupportsSearching { get { return false; } }
int IBindingList.Find(PropertyDescriptor prop, object obj) { throw new
NotSupportedException(); }
bool IBindingList.SupportsChangeNotification { get { return true; } }
object IList.this[int index]
{
get { return this[index]; }
set { this[index] = (T)value; }
}


private readonly List<T> InnerList;
public FooList()
{
InnerList = new List<T>();
}
private static readonly bool raisesItemChangedEvents;
static FooList()
{
foreach (Type type in typeof(T).GetInterfaces())
{
if (type == typeof(INotifyPropertyChanged))
{
raisesItemChangedEvents = true;
break;
}
}
}
bool IRaiseItemChangedEvents.RaisesItemChangedEvents {
get {return raisesItemChangedEvents;}
}
// useful for major changes; sort etc
//(suspends notificaction then does reset)
private int editCount;
public void BeginEdit() { editCount++; }
public void EndEdit()
{
if (editCount <= 0) throw new InvalidOperationException();
editCount--;
if (editCount == 0) OnListChanged(ListChangedType.Reset, -1);
}
private bool NotificationEnabled
{
get { return editCount == 0; }
}

public event ListChangedEventHandler ListChanged;
private void OnListChanged(ListChangedType changeType, int index)
{
if (NotificationEnabled && ListChanged != null)
{
ListChanged(this, new ListChangedEventArgs(changeType, index,
index));
}
}
public T AddNew() {
// note: gets more complicated if you support ICancelAddNew
T item = Activator.CreateInstance<T>();
Add(item);
return item;
}
object IBindingList.AddNew()
{
return AddNew();
}

void WatchForChange(T item, bool enable)
{
INotifyPropertyChanged npc = item as INotifyPropertyChanged;
if (npc != null)
{
if (enable)
{
npc.PropertyChanged += ItemPropertyChanged;
}
else
{
npc.PropertyChanged -= ItemPropertyChanged;
}
}
}

void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(sender is T) {
int index = IndexOf((T)sender);
if(index>=0) {
OnListChanged(ListChangedType.ItemChanged, index);
}
}
}

public T this[int index]
{
get { return InnerList[index]; }
set
{
// if same reference, then ignore
T oldItem = this[index];
if (ReferenceEquals(oldItem, value)) return;
InnerList[index] = value;
WatchForChange(oldItem, false);
WatchForChange(value, true);
OnListChanged(ListChangedType.ItemChanged, index);
}
}

int IList.Add(object obj) { return InnerAdd((T)obj); }
public void Add(T item) { InnerAdd(item); }
private int InnerAdd(T item)
{
int index = InnerList.Count;
InnerList.Add(item);
WatchForChange(item, true);
OnListChanged(ListChangedType.ItemAdded, index);
return index;
}
public void Insert(int index, T item)
{
InnerList.Insert(index, item);
WatchForChange(item, true);
OnListChanged(ListChangedType.ItemAdded, index);
}
public bool Remove(T item)
{
int index = IndexOf(item);
if (index < 0) return false;
RemoveAt(index);
return true;
}
public void RemoveAt(int index)
{
T item = this[index];
InnerList.RemoveAt(index);
WatchForChange(item, false);
OnListChanged(ListChangedType.ItemDeleted, index);
}
public void Clear()
{
foreach (T item in InnerList)
{
WatchForChange(item, false);
}
InnerList.Clear();
OnListChanged(ListChangedType.Reset, -1);
}
}
 
T

t.mills624

OK; here is a pretty-bare-bones implementation that supports change
notification of items via INotifyPropertyChanged...

You might also consider adding support for ICancelAddNew, but this interface
is pretty backwards in my opinion - I suspect it was designed to fit how
DataTable worked at the time, rather than "how should this be done?" - and
it is *not* as trivial as removing the row...
Note that if you provide a proper ApplySort implemention then users will be
able to sort via the column headers; I've posted examples of this
previously.

I haven't added many comments due to time; but please ask any questions you
like. The main thing to note is how anything adding/removing/replacing items
in the list uses WatchForChange to subscribe/unsubscribe to the
PropertyChanged event.

Anyways... [long code warning]

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

class Bar : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void UpdateField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new
PropertyChangedEventArgs(propertyName));
        }
    }
    private string name;
    private DateTime dateOfBirth;
    public string Name
    {
        get { return name; }
        set { UpdateField(ref name, value, "Name"); }
    }
    [DisplayName("Date of Birth")]
    public DateTime DateOfBirth
    {
        get { return dateOfBirth; }
        set { UpdateField(ref dateOfBirth, value, "DateOfBirth"); }
    }}

class Program
{
    [STAThread]
    static void Main()
    {
        FooList<Bar> data = new FooList<Bar>();
        Random rand = new Random();
        Application.EnableVisualStyles();
        using(Form form = new Form())
        using(DataGridView view = new DataGridView())
        using(Button btn1 = new Button())
        using(Button btn2 = new Button())
        using (Button btn3 = new Button())
        using (Button btn4 = new Button())
        {
            view.Dock = DockStyle.Fill;
            btn1.Dock = btn2.Dock = btn3.Dock = btn4.Dock =
DockStyle.Bottom;
            view.DataSource = data;
            form.Controls.AddRange(new Control[] { view, btn1,btn2, btn3,
btn4 });
            btn1.Text = "Clear";
            btn1.Click += delegate { data.Clear(); };
            btn2.Text = "Insert";
            btn2.Click += delegate {
                Bar b = new Bar();
                b.Name = "New bar";
                int index = data.Count == 0 ? 0 : rand.Next(0, data.Count);
                data.Insert(index, b);
            };
            btn3.Text = "Remove";
            btn3.Click += delegate
            {
                if (data.Count > 0)
                {
                    data.RemoveAt(rand.Next(0, data.Count));
                }
            };
            btn4.Text = "Update";
            btn4.Click += delegate
            {
                if (data.Count > 0)
                {
                    data[rand.Next(0, data.Count)].Name += "*";
                }
            };
            Application.Run(form);
        }
    }

}

class FooList<T> : IList<T>, IBindingList, IRaiseItemChangedEvents
{
    object ICollection.SyncRoot { get { return
((ICollection)InnerList).SyncRoot; } }
    bool ICollection.IsSynchronized { get { return
((ICollection)InnerList).IsSynchronized; } }
    public int IndexOf(T item) { return InnerList.IndexOf(item); }
    int IList.IndexOf(object obj) { return IndexOf((T)obj); }
    bool IList.Contains(object obj) { return Contains((T)obj); }
    void IList.Remove(object obj) { Remove((T)obj); }
    public bool Contains(T item) {return InnerList.Contains(item);}
    public void CopyTo(T[] array, int arrayIndex) { InnerList.CopyTo(array,
arrayIndex); }
    void ICollection.CopyTo(Array array, int arrayIndex) {
((ICollection)InnerList).CopyTo(array, arrayIndex); }
    public int Count { get { return InnerList.Count; } }
    void IList.Insert(int index, object obj) { Insert(index, (T)obj); }
    bool ICollection<T>.IsReadOnly { get { return false; } }
    bool IList.IsReadOnly { get { return false; } }
    bool IBindingList.AllowNew { get { return true; } }
    bool IList.IsFixedSize { get { return false; } }
    bool IBindingList.AllowEdit { get { return true; } }
    bool IBindingList.AllowRemove { get { return true; } }
    public IEnumerator<T> GetEnumerator() { return
InnerList.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
    void IBindingList.AddIndex(PropertyDescriptor prop) { }
    void IBindingList.RemoveIndex(PropertyDescriptor prop) { }
    bool IBindingList.SupportsSorting { get { return false; } }
    bool IBindingList.IsSorted { get { return false; } }
    void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection
dir) { throw new NotSupportedException(); }
    void IBindingList.RemoveSort() { }
    PropertyDescriptor IBindingList.SortProperty { get { return null; } }
    ListSortDirection IBindingList.SortDirection { get { return
ListSortDirection.Ascending; } }
    bool IBindingList.SupportsSearching { get { return false; } }
    int IBindingList.Find(PropertyDescriptor prop, object obj) { thrownew
NotSupportedException(); }
    bool IBindingList.SupportsChangeNotification { get { return true; } }
    object IList.this[int index]
    {
        get { return this[index]; }
        set { this[index] = (T)value; }
    }

    private readonly List<T> InnerList;
    public FooList()
    {
        InnerList = new List<T>();
    }
    private static readonly bool raisesItemChangedEvents;
    static FooList()
    {
        foreach (Type type in typeof(T).GetInterfaces())
        {
            if (type == typeof(INotifyPropertyChanged))
            {
                raisesItemChangedEvents = true;
                break;
            }
        }
    }
    bool IRaiseItemChangedEvents.RaisesItemChangedEvents {
        get {return raisesItemChangedEvents;}
    }
    // useful for major changes; sort etc
    //(suspends notificaction then does reset)
    private int editCount;
    public void BeginEdit() { editCount++; }
    public void EndEdit()
    {
        if (editCount <= 0) throw new InvalidOperationException();
        editCount--;
        if (editCount == 0) OnListChanged(ListChangedType.Reset, -1);
    }
    private bool NotificationEnabled
    {
        get { return editCount == 0; }
    }

    public event ListChangedEventHandler ListChanged;
    private void OnListChanged(ListChangedType changeType, int index)
    {
        if (NotificationEnabled && ListChanged != null)
        {
            ListChanged(this, new ListChangedEventArgs(changeType, index,
index));
        }
    }
    public T AddNew() {
        // note: gets more complicated if you support ICancelAddNew
        T item = Activator.CreateInstance<T>();
        Add(item);
        return item;
    }
    object IBindingList.AddNew()
    {
        return AddNew();
    }

    void WatchForChange(T item, bool enable)
    {
        INotifyPropertyChanged npc = item as INotifyPropertyChanged;
        if (npc != null)
        {
            if (enable)
            {
                npc.PropertyChanged += ItemPropertyChanged;
            }
            else
            {
                npc.PropertyChanged -= ItemPropertyChanged;
            }
        }
    }

    void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(sender is T) {
            int index = IndexOf((T)sender);
            if(index>=0) {
                OnListChanged(ListChangedType.ItemChanged,index);
            }
        }
    }

    public T this[int index]
    {
        get { return InnerList[index]; }
        set
        {
            // if same reference, then ignore
            T oldItem = this[index];
            if (ReferenceEquals(oldItem, value)) return;
            InnerList[index] = value;
            WatchForChange(oldItem, false);
            WatchForChange(value, true);
            OnListChanged(ListChangedType.ItemChanged, index);
        }
    }

    int IList.Add(object obj) { return InnerAdd((T)obj); }
    public void Add(T item) { InnerAdd(item); }
    private int InnerAdd(T item)
    {
        int index = InnerList.Count;
        InnerList.Add(item);
        WatchForChange(item, true);
        OnListChanged(ListChangedType.ItemAdded, index);
        return index;
    }
    public void Insert(int index, T item)
    {
        InnerList.Insert(index, item);
        WatchForChange(item, true);
        OnListChanged(ListChangedType.ItemAdded, index);
    }
    public bool Remove(T item)
    {
        int index = IndexOf(item);
        if (index < 0) return false;
        RemoveAt(index);
        return true;
    }
    public void RemoveAt(int index)
    {
        T item = this[index];
        InnerList.RemoveAt(index);
        WatchForChange(item, false);
        OnListChanged(ListChangedType.ItemDeleted, index);
    }
    public void Clear()
    {
        foreach (T item in InnerList)
        {
            WatchForChange(item, false);
        }
        InnerList.Clear();
        OnListChanged(ListChangedType.Reset, -1);
    }



}- Hide quoted text -

- Show quoted text -

That's a lot to chew on :) The WatchForChange and static constructor
is really intriguing, I can't wait to start dissecting this stuff and
plugging it in. This one post potentially has answered months of
prying and digging around, it is most appreciated. I'll let you know
how things go, thanks again for the time and effort you put into this
post.
 
M

Marc Gravell

I'll let you know how things go, thanks again for the time and effort you put
into this post.

No problem; but you might want to "snip" next time you reply to
something that long ;-p
The static ctor is just to check "does this T support
INotifyPropertyChanged" once rather than on every call; nothing more.
Any specific questions, please do ask.

Marc
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top