i have this code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
namespace Bix
{
public class SettingsDataObject
{
private int id;
public int Id
{
get { return id; }
set { id = value == 0 ? Db.GetNextSettingsId() : value; }
}
private string adminEmail; public string AdminEmail {
get { return adminEmail; }
set { adminEmail = value; }
}
private int state; public int State { get { return state; } set { state = value == 0 ? 1 : value; } }
public object[] GetArray()
{
return new object[] { id, adminEmail, state };
}
public SettingsDataObject()
{
}
}
public class SettingsUIObjects : ObservableCollection<SettingsUIObject>,INotifyPropertyChanged
{
protected override void InsertItem(int index, SettingsUIObject item)
{
base.InsertItem(index, item);
// handle any EndEdit events relating to this item
item.ItemEndEdit += new SettingsUIObject.ItemEndEditEventHandler(ItemEndEditHandler);
item.PropertyChanged += new SettingsUIObject.PropertyChangedEventHandler(PropertyChanged);
}
public void ItemEndEditHandler(IEditableObject sender)
{
// simply forward any EndEdit events
if (ItemEndEdit != null)
{
ItemEndEdit(sender);
}
}
public event SettingsUIObject.ItemEndEditEventHandler ItemEndEdit;
public event SettingsUIObject.PropertyChangedEventHandler PropertyChanged;
}
public class SettingsDataProvider
{
private DataAccessLayer dl;
public SettingsDataProvider()
{
dl = new DataAccessLayer();
}
public SettingsUIObjects GetSettings()
{
try
{
SettingsUIObjects objs = new SettingsUIObjects();
List<SettingsDataObject> objDataObjects = dl.GetSettings();
foreach (SettingsDataObject obj in objDataObjects)
{
objs.Add(new SettingsUIObject(obj));
}
objs.ItemEndEdit += new SettingsUIObject.ItemEndEditEventHandler(SettingsItemEndEdit);
objs.CollectionChanged += new
NotifyCollectionChangedEventHandler(SettingsCollectionChanged);
objs.PropertyChanged += new SettingsUIObject.PropertyChangedEventHandler(SettingsPropertyChanged);
return objs;
}
catch (Exception) { return new SettingsUIObjects(); }
}
void SettingsItemEndEdit(IEditableObject sender)
{
SettingsUIObject obj = sender as SettingsUIObject;
// use the data access layer to update the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
void SettingsPropertyChanged(INotifyPropertyChanged sender, PropertyChangedEventArgs e)
{
SettingsUIObject obj = sender as SettingsUIObject;
// use the data access layer to update the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
void SettingsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object item in e.OldItems)
{
SettingsUIObject obj = item as SettingsUIObject;
// use the data access layer to delete the wrapped data object
dl.DeleteSettings(obj.GetDataObject());
}
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object item in e.NewItems)
{
SettingsUIObject obj = item as SettingsUIObject;
// use the data access layer to delete the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
}
}
}
public class SettingsUIObject : IEditableObject, INotifyPropertyChanged
{
private SettingsDataObject obj;
public SettingsUIObject(SettingsDataObject o)
{
obj = o;
}
public SettingsDataObject GetDataObject()
{
return obj;
}
public int Id { get { return obj.Id; } set { obj.Id = value; } }
public string AdminEmail {
get { return obj.AdminEmail; }
set { obj.AdminEmail = value; }
}
public delegate void ItemEndEditEventHandler(IEditableObject sender);
public event ItemEndEditEventHandler ItemEndEdit;
#region IEditableObject Members
public void BeginEdit() { }
public void CancelEdit() { }
public void EndEdit()
{
if (ItemEndEdit != null)
{
ItemEndEdit(this);
}
}
#endregion
public delegate void PropertyChangedEventHandler(INotifyPropertyChanged sender, PropertyChangedEventArgs e);
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
and i keep getting the compile error:
'Bix.SettingsUIObject' does not implement interface member 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged'. 'Bix.SettingsUIObject.PropertyChanged' cannot implement 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged' because it does not have the matching return type of 'System.ComponentModel.PropertyChangedEventHandler'
can anyone tell me why?
thanks
Orson
public delegate void PropertyChangedEventHandler(INotifyPropertyChanged sender, PropertyChangedEventArgs e);
public event PropertyChangedEventHandler PropertyChanged;
Your code redeclares a PropertyChangedEventHandler delegate, which hides the one declared in System.ComponentModel. So your event is of type SettingsUIObject.PropertyChangedEventHandler, not System.ComponentModel.PropertyChangedEventHandler. Since the type doesn't match the one declared in INotifyPropertyChanged, your PropertyChanged event doesn't a valid implementation of the interface.
Just remove your PropertyChangedEventHandler delegate and it should work fine.
Related
I created a Class EventList inheriting List which fires an Event each time something is Added, Inserted or Removed:
public class EventList<T> : List<T>
{
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
public new void Add(T item)
{
base.Add(item);
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
...
}
At the Moment I use it as a Property like this:
public EventList List
{
get { return m_List; }
set
{
m_List.ListChanged -= List_ListChanged;
m_List = value;
m_List.ListChanged += List_ListChanged;
List_ListChanged();
}
}
Now my Problem is, can I somehow handle if a new Object is referred to it or prevent that, so I do not have to do the event wiring stuff in the setter?
Of course, I can change the property to "private set" but I would like to be able to use the class as variable as well.
You seldom create a new instance of a collection class in a class. Instantiate it once and clear it instead of creating a new list. (and use the ObservableCollection since it already has the INotifyCollectionChanged interface inherited)
private readonly ObservableCollection<T> list;
public ctor() {
list = new ObservableCollection<T>();
list.CollectionChanged += listChanged;
}
public ObservableCollection<T> List { get { return list; } }
public void Clear() { list.Clear(); }
private void listChanged(object sender, NotifyCollectionChangedEventArgs args) {
// list changed
}
This way you only have to hook up events once, and can "reset it" by calling the clear method instead of checking for null or equality to the former list in the set accessor for the property.
With the changes in C#6 you can assign a get property from a constructor without the backing field (the backing field is implicit)
So the code above can be simplified to
public ctor() {
List = new ObservableCollection<T>();
List.CollectionChanged += OnListChanged;
}
public ObservableCollection<T> List { get; }
public void Clear()
{
List.Clear();
}
private void OnListChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// react to list changed
}
ObservableCollection is a List with a CollectionChanged event
ObservableCollection.CollectionChanged Event
For how to wire up the event handler see answer from Patrick. +1
Not sure what you are looking for but I use this for a collection with one event that fires on add, remove, and change.
public class ObservableCollection<T>: INotifyPropertyChanged
{
private BindingList<T> ts = new BindingList<T>();
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged( String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public BindingList<T> Ts
{
get { return ts; }
set
{
if (value != ts)
{
Ts = value;
if (Ts != null)
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
NotifyPropertyChanged("Ts");
}
}
}
private static void OnListChanged(ObservableCollection<T> vm)
{
// this will fire on add, remove, and change
// if want to prevent an insert this in not the right spot for that
// the OPs use of word prevent is not clear
// -1 don't be a hater
vm.NotifyPropertyChanged("Ts");
}
public ObservableCollection()
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
}
If you do not want to or can not convert to an Observable Collection, try this:
public class EventList<T> : IList<T> /* NOTE: Changed your List<T> to IList<T> */
{
private List<T> list; // initialize this in your constructor.
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
private void notify()
{
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
public new void Add(T item)
{
list.Add(item);
notify();
}
public List<T> Items {
get { return list; }
set {
list = value;
notify();
}
}
...
}
Now, for your property, you should be able to reduce your code to this:
public EventList List
{
get { return m_List.Items; }
set
{
//m_List.ListChanged -= List_ListChanged;
m_List.Items = value;
//m_List.ListChanged += List_ListChanged;
//List_ListChanged();
}
}
Why? Setting anything in the EventList.Items will call your private notify() routine.
I have a Solution for when someone calls the Generic method from IList.add(object). So that you also get notified.
using System;
using System.Collections;
using System.Collections.Generic;
namespace YourNamespace
{
public class ObjectDoesNotMatchTargetBaseTypeException : Exception
{
public ObjectDoesNotMatchTargetBaseTypeException(Type targetType, object actualObject)
: base(string.Format("Expected base type ({0}) does not match actual objects type ({1}).",
targetType, actualObject.GetType()))
{
}
}
/// <summary>
/// Allows you to react, when items were added or removed to a generic List.
/// </summary>
public abstract class NoisyList<TItemType> : List<TItemType>, IList
{
#region Public Methods
/******************************************/
int IList.Add(object item)
{
CheckTargetType(item);
Add((TItemType)item);
return Count - 1;
}
void IList.Remove(object item)
{
CheckTargetType(item);
Remove((TItemType)item);
}
public new void Add(TItemType item)
{
base.Add(item);
OnItemAdded(item);
}
public new bool Remove(TItemType item)
{
var result = base.Remove(item);
OnItemRemoved(item);
return result;
}
#endregion
# region Private Methods
/******************************************/
private static void CheckTargetType(object item)
{
var targetType = typeof(TItemType);
if (item.GetType().IsSubclassOf(targetType))
throw new ObjectDoesNotMatchTargetBaseTypeException(targetType, item);
}
#endregion
#region Abstract Methods
/******************************************/
protected abstract void OnItemAdded(TItemType addedItem);
protected abstract void OnItemRemoved(TItemType removedItem);
#endregion
}
}
If an ObservableCollection is not the solution for you, you can try that:
A) Implement a custom EventArgs that will contain the new Count attribute when an event will be fired.
public class ChangeListCountEventArgs : EventArgs
{
public int NewCount
{
get;
set;
}
public ChangeListCountEventArgs(int newCount)
{
NewCount = newCount;
}
}
B) Implement a custom List that inherits from List and redefine the Count attribute and the constructors according to your needs:
public class CustomList<T> : List<T>
{
public event EventHandler<ChangeListCountEventArgs> ListCountChanged;
public new int Count
{
get
{
ListCountChanged?.Invoke(this, new ChangeListCountEventArgs(base.Count));
return base.Count;
}
}
public CustomList()
{ }
public CustomList(List<T> list) : base(list)
{ }
public CustomList(CustomList<T> list) : base(list)
{ }
}
C) Finally subscribe to your event:
var myList = new CustomList<YourObject>();
myList.ListCountChanged += (obj, e) =>
{
// get the count thanks to e.NewCount
};
I am updating a Datagrid and when a user inputs a number that already exists I want notify the user they the number already exists and then clear the value from the datagrid.
I know why this is happening, but I can't figure out how to stop this or how to make a work around.
This is very simplified code: Using EF code first with MVVM model.
public partial class StaffMasterData
{
public System.Guid Id { get; set; } // ID (Primary key)
public int? StaffNo { get; set; } // StaffNo
public StaffMasterData()
{
InitializePartial();
}
partial void InitializePartial();
}
Entity extension class for StaffMasterData :
public partial class StaffMasterData : INotifyPropertyChanged
{
partial void InitializePartial()
{
Id = Guid.NewGuid();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And the method to save the data:
public void SaveMasterData(StaffMasterData nwRowData)
{
using (var db = CreateDbContext())
{
//MasterDataBinding is the observableCollection
//the datagrid is being bound to.
var staffNoExists = MasterDataBinding.Any(p => p.StaffNo == nwRowData.StaffNo);
if (!staffNoExists)
{
db.StaffMasterDatas.AddOrUpdate(nwRowData);
db.SaveChanges();
}
else
{
Alerts.Error("Staff Number exists");
nwRowData.StaffNo = null;
}
}
}
And the assinging of the collection changed event:
public class ShiftManagerViewModel : INotifyPropertyChanged
{
private ObservableCollection<StaffMasterData> _mMasterDataBinding = new ObservableCollection<StaffMasterData>();
public ObservableCollection<StaffMasterData> MasterDataBinding
{
get { return _mMasterDataBinding; }
set
{
if (value != _mMasterDataBinding)
{
_mMasterDataBinding = value;
OnPropertyChanged();
}
}
}
public ShiftManagerViewModel()
{
_mMasterDataBinding.CollectionChanged += collectionChanged_Event;
}
private void collectionChanged_Event(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.NewItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged += propertyChanged_Event;
}
}
if (e.OldItems != null && e.OldItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.OldItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged -= propertyChanged_Event;
}
}
}
public void propertyChanged_Event(object sender, PropertyChangedEventArgs e)
{
if (sender is StaffMasterData)
{
SaveMasterData((StaffMasterData)sender);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
As it is probably very clear, when running through this line of code nwRowData.StaffNo = null; , it fires the event again as the collection has been modified which then in turn runs through the messageBox code and it pops up twice.
Honestly I have hit a brick wall with this and any point in the right direction would be appreciated.
You could use a flag that determines whether to actually call the SaveMasterData method. Set this flag to false just before you set the StaffNo property to null and then set it back to true immediately afterwards:
private bool _handle = true;
public void SaveMasterData(StaffMasterData nwRowData)
{
using (var db = CreateDbContext())
{
//MasterDataBinding is the observableCollection
//the datagrid is being bound to.
var staffNoExists = MasterDataBinding.Any(p => p.StaffNo == nwRowData.StaffNo);
if (!staffNoExists)
{
db.StaffMasterDatas.AddOrUpdate(nwRowData);
db.SaveChanges();
}
else
{
Alerts.Error("Staff Number exists");
_handle = false;
nwRowData.StaffNo = null;
_handle = true;
}
}
}
public void propertyChanged_Event(object sender, PropertyChangedEventArgs e)
{
if (!_handle && sender is StaffMasterData)
{
SaveMasterData((StaffMasterData)sender);
}
}
I have one button on my MasterDetailPage changing the value on an INT (named App.value1) depending on what you click looking like this:
void click1 (object s, EventArgs a)
{
if (App.value1 == 0) {
App.value1 = App.value1 + 1;
} else {
App.value1 = 0;
}
}
And I want this click function to immediately change the value on my StartPage (another ContentPage). So I have created a viewmodel looking like this, where I try to work with the current value:
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
public int currentValue {
get {
return App.value1;
}
set {
if (App.value1 == 0) {
App.value1 = 0;
} else {
App.value1 = 1;
}
}
}
And this is the StartPage where I want the value of the INT to update immediately depending on what you clicked on at the MasterDetailView.
public StartPage ()
{
var ourView = new StartPageViewModel ();
ourCurrentValue = ourView.currentValue;
}
protected async override void OnAppearing()
{
LoadData();
}
private async Task<List<Pin>> LoadData() //I work with pins here (not showing that code though as it is irrelavant.
{
if (ourCurrentValue == 0) {
System.Diagnostics.Debug.WriteLine ("Value is 0");
}
else {
System.Diagnostics.Debug.WriteLine ("Value is 1");
}
}
Right now I only see "Value is 0" in my log. Nothing updates when I click on my button on the MasterDetailPage.
UPDATED CODE:
public class StartPageViewModel : INotifyPropertyChanged
{
private ICommand clickCommand;
private int currentValue;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
public StartPageViewModel()
{
ClickCommand = new Command(() => CurrentValue = CurrentValue + 1);
}
public ICommand ClickCommand
{
get { return clickCommand; }
set
{
clickCommand = value;
OnPropertyChanged("ClickCommand");
}
}
public int CurrentValue
{
get { return currentValue; }
set
{
currentValue = value;
OnPropertyChanged("CurrentValue");
}
}
}
And StartPage:
public StartPage ()
{
App.PropertyChanged += (sender, args) => OnPropertyChanged("currentValue"); // ERROR: `An object reference is requiered to access non-static member 'Xamarin.Forms.BindableObject.PropertyChanged`
}
You can proceed with something like that:
Make following changes to your App class and value1 property inside that class:
public static event PropertyChangedEventHandler PropertyChanged;
private static void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged (null, new PropertyChangedEventArgs (propertyName));
}
}
private static int _value1;
public static int value1
{
get { return _value1; }
set
{
_value1 = value;
OnPropertyChanged("value1");
}
}
Then add this line to your StartPageViewModel constructor:
App.PropertyChanged += (sender, args) => OnPropertyChanged("currentValue");
In that code you are just leveraging PropertyChanged for your own purposes (you can even create your own event for that).
I mean StartPageViewModel subscribes to PropertyChanged event in Appclass, so it will be notified when value1 change. And when it actually occurs, then it is invoking his own PropertyChanged to notify View about currentValue change.
However, I would say better solution is to share View Model between MasterDetailPage and StartPage, because using global state makes your solution hard to understand :
public class SharedViewModel : INotifyPropertyChanged
{
private ICommand clickCommand;
private int currentValue;
/* INotifyPropertyChanged implementation */
public SharedViewModel()
{
ClickCommand = new Command(() => CurrentValue = CurrentValue + 1);
}
public ICommand ClickCommand
{
get { return clickCommand; }
set
{
clickCommand = value;
OnPropertyChanged("ClickCommand");
}
}
public int CurrentValue
{
get { return currentValue; }
set
{
currentValue = value;
OnPropertyChanged("CurrentValue");
}
}
}
And you need to use the same instance of SharedViewModel in MasterDetailPage as well as StartPage
I have a base DependencyObject class where I have a method that takes an object, gets the properties, and for each property that is a type that implements INotifyPropertyChanged, I add a new PropertyChangedEventHandler. Now in the handler method, it gets the parameters of an object "sender" and the PropertyChangedEventArgs "e". My question is, does anyone know how to dynamically get the property name if sender is a property of a type that implements the INotifyPropertyChanged.
Here is what I'm working with:
public class BaseDependencyObject : DependencyObject, INotifyPropertyChanged
{
public BaseDependencyObject()
{
}
protected void SetValues(Object thisObject, Object entity)
{
try
{
PropertyInfo[] properties = entity.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
var value = property.GetValue(entity, null);
var valueIsEntity = value is System.ServiceModel.DomainServices.Client.Entity;
var thisObjectsProperty = thisObject.GetType().GetProperty(property.Name);
if (thisObjectsProperty != null && value != null)
{
if (valueIsEntity)
{
if (thisObjectsProperty.PropertyType.GetInterface("INotifyPropertyChanged", true) != null)
{
var propertyInstance = Activator.CreateInstance(thisObjectsProperty.PropertyType);
((INotifyPropertyChanged)propertyInstance).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
SetValues(thisObjectsProperty, value);
}
else if (thisObjectsProperty.PropertyType.GetInterface("ICollection", true) != null
&& thisObjectsProperty.PropertyType.GetGenericArguments().Count() > 0)
{
Type genericType = thisObjectsProperty.PropertyType.GetGenericArguments()[0];
var observableCollection = Activator.CreateInstance(thisObjectsProperty.PropertyType) as IList;
if (observableCollection is INotifyCollectionChanged)
((INotifyCollectionChanged)observableCollection).CollectionChanged += this.Object_CollectionChanged;
if (observableCollection is INotifyPropertyChanged)
((INotifyPropertyChanged)observableCollection).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
foreach (var item in (IEnumerable)value)
{
var newItem = Activator.CreateInstance(genericType);
if (newItem != null)
{
SetValues(newItem, item);
observableCollection.Add(newItem);
}
}
}
else
{
thisObjectsProperty.SetValue(thisObject, value, null);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
protected void Object_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged -= this.Object_PropertyChanged;
}
}
break;
}
}
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.NotifyPropertyChanged(e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The SetValues method's first param is the DependencyObject type that will be used in the view model. The second param is the entity that is being returned from the DomainService's Context.LoadOperation.
What my issue boils down to is when the INotifyCollectionChanged.CollectionChanged fires I'm needing to be able to raise the PropertyChanged event with the collection's property name. So if anyone has any advise I would greatly appreciate it. Thanks in advance.
Edit
Figured out how to get the properties name that is firing the event. Here is an edited version of my PropertyChangedEventHandler.
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var properties = this.GetType().GetProperties().Where(x => x.PropertyType == sender.GetType()).ToArray();
foreach (var property in properties)
{
this.NotifyPropertyChanged(property.Name);
}
//this.NotifyPropertyChanged(e.PropertyName);
}
Basically this does what I was looking for, but aparentyly I am still not doing something right. The UIElement is still not updating when the ObservableCollection that is a property of another type is being added to.
Here is an example of my DependencyObjects and ViewModel:
public class LOB : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(LOB), null);
public ObservableCollection<Group> Groups
{
get { return (ObservableCollection<Group>)GetValue(GroupsProperty); }
set
{
SetValue(GroupsProperty, value);
NotifyPropertyChanged("Groups");
}
}
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.Register("Groups", typeof(ObservableCollection<Group>), typeof(LOB), new PropertyMetadata(null, OnGroupsPropertyChanged));
static void OnGroupsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += new NotifyCollectionChangedEventHandler(((LOB)obj).Object_CollectionChanged);
((INotifyPropertyChanged)e.NewValue).PropertyChanged += new PropertyChangedEventHandler(((LOB)obj).Object_PropertyChanged);
}
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= ((LOB)obj).Object_CollectionChanged;
((INotifyPropertyChanged)e.OldValue).PropertyChanged -= ((LOB)obj).Object_PropertyChanged;
}
}
}
public class Group : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(Group), null);
public String GroupName
{
get { return (String)GetValue(GroupNameProperty); }
set
{
SetValue(GroupNameProperty, value);
NotifyPropertyChanged("GroupName");
}
}
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.Register("GroupName", typeof(String), typeof(Group), null);
}
public class MyViewModel : DependencyObject
{
public static readonly DependencyProperty LobCollectionProperty =
DependencyProperty.Register("LobCollection",
typeof(ObservableCollection<LOB>),
typeof(MyViewModel),
new PropertyMetadata(null, LobCollectionPropertyChanged));
public ObservableCollection<LOB> LobCollection
{
get { return (ObservableCollection<MainBusinessLine>)GetValue(LobCollectionPropertyChanged); }
set
{
SetValue(MainBusinessLineCollectionProperty, value);
NotifyPropertyChanged("LobCollection");
}
}
static void LobCollectionPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var viewModel = obj as MyViewModel;
if (viewModel == null)
return;
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= viewModel.LobCollection_Changed;
}
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += viewModel.LobCollection_Changed;
}
}
void LobCollection_Changed(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("LobCollection");
}
}
After our conversation above, this is rather moot, but I thought about how I'd implement a base class that fired PropertyChanged events when a collection changed in a property that was defined by the subclass. As I said, it's a bit non-standard, but here's how I'd do it.
class FancyCollectionAndPropertyChangedBase : INotifyPropertyChanged
{
private Dictionary<ICollectionChanged, String> collectionNameLookup = new Dictionary<ICollectionChanged, String>();
protected FancyCollectionAndPropertyChangedBase()
{
this.PropertyChanged += MyPropertyChanged;
}
private void MyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(this.collectionNameLookup.ContainsValue(e.PropertyName)
{
KeyValuePair<INotifyCollectionChanged, String> oldValue = this.collectionNameLookup.First(kvp => kvp.Value == e.Name);
oldValue.Key -= MyCollectionChanged;
this.collecitonNameLookup.Remove(oldValue.Key);
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
else if(typeof(INotifyCollectionChanged).IsAssignableFrom(this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).PropertyType))
{
// Note: I may have gotten the IsAssignableFrom statement, above, backwards.
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
}
private void MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.NotifyPropertyChanged(this.collectionNameLookup[sender];
}
}
I have a gridview and, when a record is double-clicked, I want it to open up a new detail-view form for that particular record.
As an example, I have created a Customer class:
using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SqlClient;
using System.Collections;
namespace SyncTest
{
#region Customer Collection
public class CustomerCollection : BindingListView<Customer>
{
public CustomerCollection() : base()
{
}
public CustomerCollection(List<Customer> customers) : base(customers)
{
}
public CustomerCollection(DataTable dt)
{
foreach (DataRow oRow in dt.Rows)
{
Customer c = new Customer(oRow);
this.Add(c);
}
}
}
#endregion
public class Customer : INotifyPropertyChanged, IEditableObject, IDataErrorInfo
{
private string _CustomerID;
private string _CompanyName;
private string _ContactName;
private string _ContactTitle;
private string _OldCustomerID;
private string _OldCompanyName;
private string _OldContactName;
private string _OldContactTitle;
private bool _Editing;
private string _Error = string.Empty;
private EntityStateEnum _EntityState;
private Hashtable _PropErrors = new Hashtable();
public event PropertyChangedEventHandler PropertyChanged;
private void FirePropertyChangeNotification(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public Customer()
{
this.EntityState = EntityStateEnum.Unchanged;
}
public Customer(DataRow dr)
{
//Populates the business object item from a data row
this.CustomerID = dr["CustomerID"].ToString();
this.CompanyName = dr["CompanyName"].ToString();
this.ContactName = dr["ContactName"].ToString();
this.ContactTitle = dr["ContactTitle"].ToString();
this.EntityState = EntityStateEnum.Unchanged;
}
public string CustomerID
{
get
{
return _CustomerID;
}
set
{
_CustomerID = value;
FirePropertyChangeNotification("CustomerID");
}
}
public string CompanyName
{
get
{
return _CompanyName;
}
set
{
_CompanyName = value;
FirePropertyChangeNotification("CompanyName");
}
}
public string ContactName
{
get
{
return _ContactName;
}
set
{
_ContactName = value;
FirePropertyChangeNotification("ContactName");
}
}
public string ContactTitle
{
get
{
return _ContactTitle;
}
set
{
_ContactTitle = value;
FirePropertyChangeNotification("ContactTitle");
}
}
public Boolean IsDirty
{
get
{
return ((this.EntityState != EntityStateEnum.Unchanged) || (this.EntityState != EntityStateEnum.Deleted));
}
}
public enum EntityStateEnum
{
Unchanged,
Added,
Deleted,
Modified
}
void IEditableObject.BeginEdit()
{
if (!_Editing)
{
_OldCustomerID = _CustomerID;
_OldCompanyName = _CompanyName;
_OldContactName = _ContactName;
_OldContactTitle = _ContactTitle;
}
this.EntityState = EntityStateEnum.Modified;
_Editing = true;
}
void IEditableObject.CancelEdit()
{
if (_Editing)
{
_CustomerID = _OldCustomerID;
_CompanyName = _OldCompanyName;
_ContactName = _OldContactName;
_ContactTitle = _OldContactTitle;
}
this.EntityState = EntityStateEnum.Unchanged;
_Editing = false;
}
void IEditableObject.EndEdit()
{
_Editing = false;
}
public EntityStateEnum EntityState
{
get
{
return _EntityState;
}
set
{
_EntityState = value;
}
}
string IDataErrorInfo.Error
{
get
{
return _Error;
}
}
string IDataErrorInfo.this[string columnName]
{
get
{
return (string)_PropErrors[columnName];
}
}
private void DataStateChanged(EntityStateEnum dataState, string propertyName)
{
//Raise the event
if (PropertyChanged != null && propertyName != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//If the state is deleted, mark it as deleted
if (dataState == EntityStateEnum.Deleted)
{
this.EntityState = dataState;
}
if (this.EntityState == EntityStateEnum.Unchanged)
{
this.EntityState = dataState;
}
}
}
}
Here is my the code for the double-click event:
private void customersDataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
Customer oCustomer = (Customer)customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position];
CustomerForm oForm = new CustomerForm();
oForm.NewCustomer = oCustomer;
oForm.ShowDialog(this);
oForm.Dispose();
oForm = null;
}
Unfortunately, when this code runs, I receive an InvalidCastException error stating "Unable to cast object to type 'System.Data.DataRowView' to type 'SyncTest.Customer'".
This error occurs on the very first line of that event:
Customer oCustomer = (Customer)customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position];
What am I doing wrong?... and what can I do to fix this? Any help is greatly appreciated.
Thanks!
EDIT:
Here is how the data is populated:
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'northwindDataSet.Customers' table. You can move, or remove it, as needed.
this.customersTableAdapter.Fill(this.northwindDataSet.Customers);
}
It looks like your object is of type System.Data.DataRowView, not Customer. Your code returns Dataset instead of objects that you are expecting. You need to modify the code that returns your objects.
How about using BindingSource.Current instead of CurrentManager? But from the looks of it, your data source is a DataSet.
private void customersDataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
DataGridView grid = (DataGridView) sender;
Customer customer = (Customer) grid.Rows[e.RowIndex].DataBoundItem;
}
Checks removed for brevity
Customer oCustomer = (Customer)
((DataRowView)customersBindingSource.Current).Row;
When you get the invalid cast as you have described it is due to the object is not the type you think it should be.
You can avoid the exception by using the as keyword.
Customer oCustomer = (Customer)customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position];
becomes
Customer oCustomer = customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position] as Customer;
If it is an invalid cast then oCustomer will be null.
Now if you are still getting the null then the object from List is not a Customer object. I would use the debugger to determine what the object is and how you would go about casting it to your Cusomter object.
You will never be able to convert directly to the Customer object because your error is
InvalidCastException error stating "Unable to cast object to type 'System.Data.DataRowView' to type 'SyncTest.Customer'". is giving you an DataRowView object. You need take the row and pull the values from the row.