I have a collection of classes contained in a ObservibaleCollection<MyObj> and MyObj implements INotifyPropertyChanged, but I need a property located outside of it that references a property in the collection via linq and creates its own collection to update on both the collection change and any of its content linq bound properties changing.
For sake of argument and simplicity lets say my class MyObj contains a property called IsVisible. I want a property that implements its own INotifyPropertyChanged to get a list of MyObj where IsVisible == true and keep it up to date regardless id the collection of MyObj changes or the IsVisible property does.
Is this possible without attaching to the collection changed event and subsequently just directly attaching to each child MyObj.IsVisible property? Is there a way to get INotify to bubble up through linq?
public class MyObj:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsVisible
{
get { return _IsVisible; }
protected set { if (value != _IsVisible) { _IsVisible= value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsVisible")); } }
}
private bool _IsVisible;
}
public class Foo
{
ObservableCollection<MyObj> myObjs = new ObservableCollection<MyObj>();
ObservableCollection<MyObj> myVisibleObjs {
get{
return myObjs.where(o => o.IsVisible);
}
}
}
I hope what I'm asking makes sense.
You could make use of Reactive Extensions, but for this specific requirement - maintaining a myVisibleObjs - I would use the dynamic data lib.
Try out the following:
static void Main(string[] args)
{
Foo f = new Foo();
f.BindToVisibleObjects();
// add more dummy data
f.Add(false);
f.Add(true);
// There will be 2 visible objects in MyVisibleObjects
foreach (var d in f.MyVisibleObjects)
{
Console.WriteLine(d.IsVisible);
}
Console.ReadKey();
}
public class Foo
{
ObservableCollection<MyObj> myObjs = new ObservableCollection<MyObj>();
public ReadOnlyObservableCollection<MyObj> MyVisibleObjects;
public Foo()
{
// add some dummy data
myObjs.Add(new MyObj() { IsVisible = true });
myObjs.Add(new MyObj() { IsVisible = false });
}
public void BindToVisibleObjects()
{
myObjs.ToObservableChangeSet()
.Filter(o => o.IsVisible)
.Bind(out MyVisibleObjects)
.DisposeMany()
.Subscribe();
}
public void Add(bool vis)
{
myObjs.Add(new MyObj() { IsVisible = vis });
}
}
The key here is that we bind the filtered observable changeset to a new collection that will be updated as your myObjs changes.
Related
I have a form with options. If any property value of the options change, I have to do another action.
What is the best way to check if the list of Option have changed? The Class Option is very nested, so it is hard to check it with a foreach.
I thought to save at the beginning the hashCode of the List. And compare the hashCode at the end.
List<Option> optionLit = GetOptionList();
A quick an easy way to do this would be to serialise the list at the beginning (of any operations) and then serialise the list after you operations are complete and compare the two strings.
You can serialise the list like this:
List<Option> optionList = GetOptionList();
string jsonString = JsonSerializer.Serialize(optionList);
Note: you will need to include the System.Text.Json namespace to use the JsonSerializer class: using System.Text.Json;
Upvoted for DiplomacyNotWar and YungDeiza solutions and here mine (just in case):
Create this class:
public abstract class NotifyPropertyChangeBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then inherite it in your class:
public class MyMainObject: NotifyPropertyChangeBase
{
public bool HasChanged { get; set; }
public MyNestedObject1 Nested1
{
get { return nested1; }
set
{
this.nested1= value;
this.HasChanged = true;
NotifyPropertyChanged();
}
}
public MyNestedObject2 Nested2
{
get { return nested2; }
set
{
this.nested2= value;
this.HasChanged = true;
NotifyPropertyChanged();
}
}
private MyNestedObject1 nested1;
private MyNestedObject2 nested2;
}
If needed, use the "NotifyPropertyChangeBase" in nested classes also.
In your GUI, bind some object/event to "HasChanged" property and you should be notified when object changed.
I am searching for a solution where i can ask a model if a property has changed. But i want to prevent to write own setter methods for all models and all their properties.
I want to use this to automatically generate a update queries based models and there changed properties. But if my model has a boolean property Test which is by default false, then i can't differentiate if the value is from the request payload or if it is the default value.
I already saw the INotifyPropertyChanged Implementation but there i have to write a setter for all properties too.
public class Main
{
public static void main()
{
var person = new Person();
Console.WriteLine(person.HasChanged("Firstname")); // false
Console.WriteLine(person.HasChanged("Lastname")); // false
Console.WriteLine(person.HasChanged("LikesChocolate")); // false
person.Firstname = "HisFirstname";
person.LikesChocolate = true;
Console.WriteLine(person.HasChanged("Firstname")); // true
Console.WriteLine(person.HasChanged("Lastname")); // false
Console.WriteLine(person.HasChanged("LikesChocolate")); // true
}
}
public class Person : BaseModel
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public bool LikesChocolate { get; set; }
}
public class BaseModel
{
public bool HasChanged(string propertyName)
{
// ...
}
}
I'd probably reuse the idea from WPF with their INotifyPropertyChanged pattern and simplify it a bit for the current needs. However, it resolves the question only partially, as you still need to write setters. But at least, you don't need to manage each property on its own.
So, the solution will be something like this:
void Main()
{
var person = new Person();
Console.WriteLine(person.HasChanged(nameof(Person.FirstName))); // false
Console.WriteLine(person.HasChanged(nameof(Person.LastName))); // false
Console.WriteLine(person.HasChanged(nameof(Person.LikesChocolate))); // false
person.FirstName = "HisFirstname";
person.LikesChocolate = true;
Console.WriteLine(person.HasChanged(nameof(Person.FirstName))); // true
Console.WriteLine(person.HasChanged(nameof(Person.LastName))); // false
Console.WriteLine(person.HasChanged(nameof(Person.LikesChocolate))); // true
}
public class Person : ChangeTrackable
{
private string _firstName;
private string _lastName;
private bool _likesChocolate;
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
public string LastName
{
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
public bool LikesChocolate
{
get { return _likesChocolate; }
set { SetProperty(ref _likesChocolate, value); }
}
}
public class ChangeTrackable
{
private ConcurrentDictionary<string, bool> _changes =
new ConcurrentDictionary<string, bool>();
public bool HasChanged(string propertyName)
{
return _changes.TryGetValue(propertyName, out var isChanged)
? isChanged : false;
}
public void ResetChanges()
{
_changes.Clear();
}
protected void SetProperty<T>(
ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (!Equals(storage, value))
{
_changes[propertyName] = true;
}
}
}
The ChangeTrackable tracks if property was changed and does it without any reflection that guarantees high performance. Note, that with this implementation you need to call ResetChanges if you initialize property with some actual values after constructing the object. Drawback is that you need to write each property with its backing field and call SetProperty. On the other side, you decide what to track, that could be handy in the future in your application. Also we don't need to write property as strings (thanks to compile-time CallerMemberName and nameof) that simplifies refactorings.
INotifyPropertyChanged is the established practice for this type of requirement. Part of keeping your code maintainable is by keeping it predictable and by adopting best practices and patterns.
An alternative, which I wouldn't recommend, would be to use reflection to iterate over all of your properties and dynamically add a property changed event handler. This handler could then set a boolean flag which can be returned by your HasChanges method. Please refer to this for a staring point: AddEventHandler using reflection
I would recommend avoiding unnecessary complexity though and stick with PropertyChanged notifications in your setters.
As followup for my comment a proof of concept (online):
using System.Reflection;
public class HasChangedBase
{
private class PropertyState
{
public PropertyInfo Property {get;set;}
public Object Value {get;set;}
}
private Dictionary<string, PropertyState> propertyStore;
public void SaveState()
{
propertyStore = this
.GetType()
.GetProperties()
.ToDictionary(p=>p.Name, p=>new PropertyState{Property = p, Value = p.GetValue(this)});
}
public bool HasChanged(string propertyName)
{
return propertyStore != null
&& propertyStore.ContainsKey(propertyName)
&& propertyStore[propertyName].Value != propertyStore[propertyName].Property.GetValue(this);
}
}
public class POCO : HasChangedBase
{
public string Prop1 {get;set;}
public string Prop2 {get;set;}
}
var poco = new POCO();
poco.Prop1 = "a";
poco.Prop2 = "B";
poco.SaveState();
poco.Prop2 = "b";
poco.HasChanged("Prop1");
poco.HasChanged("Prop2");
Be aware, that reflection may reduce the performance of your application when used extensively.
I would like to invoke a method when into my List property were added specific count of items. Actually I need to find way how to automatically check if a Collection has given count of items and after that call a method.
Ok, one example. I have a class MyClass
public class MyClass
{
public List<string> Url { get; set; }
public void fullList()
{
//some work with the List
}
}
And now somewhere in main function I create instance of MyClass and add some items.
MyClass mc = new MyClass();
mc.Url = new List<string>();
mc.Url.Add("www.1.com");
mc.Url.Add("www.2.com");
//now on 3rd added item I want to invoke method fullList from class MyClass
mc.Url.Add("www.3.com");
You should probably create have a class that encapsulates the URL list and the behavior you want. I guess the behavior is that the list gets "full" when it hits 3 items and then you want an event to be raised that MyClass can listen on.
class MyUrls
{
List<string> _urls = new List<string>();
public void Add(string url)
{
_urls.Add(url);
if (_urls.Count == 3 && OnFull != null)
OnFull.Invoke();
}
public IEnumerable<string> Urls
{
get
{
return _urls;
}
}
public event Action OnFull;
}
and use it like this:
public class MyClass
{
public MyClass()
{
Urls.OnFull += fullList;
}
public MyUrls Urls { get; }
public void fullList()
{
//some work with the List
}
}
MyClass mc = new MyClass();
mc.Urls = new List<string>();
mc.Urls.Add("www.1.com");
mc.Urls.Add("www.2.com");
//now on 3rd added item I want to invoke method fullList from class MyClass
mc.Urls.Add("www.3.com");
Don't reinvent the wheel - use the ObservableCollection<T>.
You can attach a handler to the CollectionChanged event that gets called when the collection is modified.
ObservableCollection<string> collection = new ObservableCollection<string>();
collection.CollectionChanged += (sender, args) => {
if(args.Action == NotifyCollectionChangedAction.Add &&
collection.Count == 3)
HandleItemsAdded(args.NewItems.Cast<string>());
};
collection.Add("www.1.com");
collection.Add("www.2.com");
collection.Add("www.3.com");
Handler:
public static void HandleItemsAdded(IEnumerable<string> newItems)
{
foreach(var item in newItems)
Console.WriteLine(item);
}
Working Fiddle: http://dotnetfiddle.net/eIeilP
I'm updating an ObservableCollection of a WPF ViewModel in a WCF Data Service asynchronous query callback method:
ObservableCollection<Ent2> mymodcoll = new ObservableCollection<Ent2>();
...
query.BeginExecute(OnMyQueryComplete, query);
...
private void OnMyQueryComplete(IAsyncResult result)
{
...
var repcoll = query.EndExecute(result);
if (mymodcoll.Any())
{
foreach (Ent c in repcoll)
{
var myItem = mymodcoll.Where(p => p.EntID == c.EntID).FirstOrDefault();
if (myItem != null)
{
myItem.DateAndTime = c.DateAndTime; // here no problems
myItem.Description = c.Description;
...
}
else
{
mymodcoll.Add(new Ent2 //here I get a runtime error
{
EntID = c.EntID,
Description = c.Description,
DateAndTime = c.DateAndTime,
...
});
}
}
}
else
{
foreach (Ent c in repcoll)
{
mymodcoll.Add(new Ent2 //here, on initial filling, there's no error
{
EntID = c.EntID,
Description = c.Description,
DateAndTime = c.DateAndTime,
...
});
}
}
}
The problem is, when a query result collection contains an item which is not present in the target collection and I need to add this item, I get a runtime error: The calling thread cannot access this object because a different thread owns it. (I pointed out this line of code by a comment)
Nevertheless, if the target collection is empty (on initial filling) all items have been added without any problem. (This part of code I also pointed out by a comment). When an item just needs to update some of its fields, there are no problems as well, the item gets updated ok.
How could I fix this issue?
First case: Here you a modifying an object in the collection, not the collection itself - thus the CollectionChanged event isn't fired.
Second case: here you are adding a new element into the collection from a different thread, the CollectionChanged event is fired. This event needs to be executed in the UI thread due to data binding.
I encountered that problem several times already, and the solution isn't pretty (if somebody has a better solution, please tell me!). You'll have to derive from ObservableCollection<T> and pass it a delegate to the BeginInvoke or Invoke method on the GUI thread's dispatcher.
Example:
public class SmartObservableCollection<T> : ObservableCollection<T>
{
[DebuggerStepThrough]
public SmartObservableCollection(Action<Action> dispatchingAction = null)
: base()
{
iSuspendCollectionChangeNotification = false;
if (dispatchingAction != null)
iDispatchingAction = dispatchingAction;
else
iDispatchingAction = a => a();
}
private bool iSuspendCollectionChangeNotification;
private Action<Action> iDispatchingAction;
[DebuggerStepThrough]
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!iSuspendCollectionChangeNotification)
{
using (IDisposable disposeable = this.BlockReentrancy())
{
iDispatchingAction(() =>
{
base.OnCollectionChanged(e);
});
}
}
}
[DebuggerStepThrough]
public void SuspendCollectionChangeNotification()
{
iSuspendCollectionChangeNotification = true;
}
[DebuggerStepThrough]
public void ResumeCollectionChangeNotification()
{
iSuspendCollectionChangeNotification = false;
}
[DebuggerStepThrough]
public void AddRange(IEnumerable<T> items)
{
this.SuspendCollectionChangeNotification();
try
{
foreach (var i in items)
{
base.InsertItem(base.Count, i);
}
}
finally
{
this.ResumeCollectionChangeNotification();
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
this.OnCollectionChanged(arg);
}
}
}
I have a datagridView, that is bound to a List. This List is made up of my class which contains 2 public properties, a String Name, and another List CustomList. See below:
public class MyClass2
{
public string Name
{ get; set;}
public string Description
{
get;
set;
}
}
public class MyClass
{
List<MyClass2> myList;
public string Name
{
get;
set;
}
public List<MyClass2> CustomList
{
get { return myList ?? (myList= new List<MyClass2>()); }
}
}
And then in my designer page:
List<MyClass> myClassList = new List<MyClass>();
dataGridView.DataSource = myClassList;
As it is right now, the only column that appears in the grid, is the MyClass:Name column, and the CustomList column does not show up. What I'd like is the CustomList column to show and to display something like "Collection" with the "..." button showing, and when it is clicked to have the "Collection Editor" to popup.
Does anyone know if this is possible and how to enable it? If there's a tutorial or anything that would help me out I'd appreciate that too. Thanks.
Using generics, I think, is a clean solution:
public class Sorter<T>: IComparer<T>
{
public string Propiedad { get; set; }
public Sorter(string propiedad)
{
this.Propiedad = propiedad;
}
public int Compare(T x, T y)
{
PropertyInfo property = x.GetType().GetProperty(this.Propiedad);
if (property == null)
throw new ApplicationException("El objeto no tiene la propiedad " + this.Propiedad);
return Comparer.DefaultInvariant.Compare(property.GetValue(x, null), property.GetValue(y, null));
}
}
Usage example:
string orderBy = "propertyName";
bool orderAsc = true;
List<MyExampleClass> myClassList = someMethod();
if (!string.IsNullOrEmpty(orderBy))
{
myClassList.Sort(new Sorter<MyExampleClass>(orderBy));
if (!orderAsc) myClassList.Reverse();
}
Short answer: Yes, you can do it with some code.
Long answer: To write the code is gonna be a pain in the ass, as you would have to know not only how the DataGridView behaves with custom columns, but you would need to know how to expose design time elements at runtime, which requires quite a bit of plumbing. Extensive knowledge about the PropertyGrid must also be known.
Note: This might a fun component to write. (I might actually tackle it if I get some time)
So using the 'button' approach posted by Dave, and some code that I found that implements the CollectionEditor, I can edit the CustomList in MyClass2
Here's my solution, although not quite as clean as I'd like:
Put this class somewhere:
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName)
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if (prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor)prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if (editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{ }
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
Add a button column to the data grid:
DataGridViewButtonColumn butt = new DataGridViewButtonColumn();
butt.HeaderText = "CustomList";
butt.Name = "CustomList";
butt.Text = "Edit CustomList...";
butt.UseColumnTextForButtonValue = true;
dataGridView.Columns.Add(butt);
dataGridView.CellClick += new DataGridViewCellEventHandler(dataGridView_CellClick);
Then call it in the button handler of the cell click.
if (e.RowIndex < 0 || e.ColumnIndex != dataGridView.Columns["CustomList"].Index)
return;
//get the name of this column
string name = (string)dataGridView[dataGridView.Columns["Name"].Index, e.RowIndex].Value;
var myClassObject= myClassList.Find(o => o.Name == name);
MyHelper.EditValue(this, myClassObject, "CustomList");
I'd still be interested in hearing other approaches, and not having to implement my own CollectionEditor. And I'm still interested in having it look more like what the TabControl uses to add TabPages in the PropertyGrid...by displaying the "..." button...but this might work for now.
What you want to do is add a column template with a button in it:
http://geekswithblogs.net/carmelhl/archive/2008/11/11/126942.aspx
In the handler for the button, get the selected MyClass item from the collection and bind its list property to a grid in your popup.