I'm working on a WPF project and have implemented a very simple way to undo one level of change which works nicely throughout the project except for one case where changes to an object's property reflects in the MemberwiseClone.
What I am doing is to do a MemberwiseClone in my object before adding or editing properties in that object, and then if the user wants to undo, I copy each property from the MemberwiseClone object back into my current object.
Because I am using WPF binding, using the MemberwiseClone is attractive to me because up until now, any change made in a property was not reflected in the MemberwiseClone. This time I have a property in my object that is an ObservableCollection of another object, and what is happening is that if I add an item to the ObservableCollection, it also gets added to the object created by MemberwiseClone and I can never truly undo.
Is there any way around this? Any thoughts you might have on this are welcomed.
Thanks.
According to Object.MemberwiseClone Remarks the object references in your ObservableCollection will be copied but not the referenced object itself. Therefore your undo collection references the same possible changed objects.
You need a deep copy, not a shallow one. Take a look at How do you do a deep copy an object in .Net (C# specifically)?
Related
I am implementing Undo/Redo for my .NET 5 application. I have an ObservableCollection of Activity objects (the collection is named Activities). Each Activity object contains two ObservableCollections "ActionItems" and "Notes". The Activities collection is displayed using a ListBox, and the ActionItems and Notes collections are displayed using two respective ItemsControls/ScrollViewers. The Activities ListBox is IsSynchronizedWithCurrentItem, which keeps the ItemsControls in synch.
After each change to an object anywhere in my application, I store a clone on the Undo/Redo stack. When it's time to unto an action on the higher level objects, e.g. an Activity, I replace the Activity object in the ObservableCollection directly:
Activities[0] = activityCloneFromUndoStack;
After this replacement, the UI updates and everything is just fine. However, when I subsequently add a new ActionItem or Note to their respective ObservableCollections on this particular Activity instance, their ItemsControls on the UI do not update. The items are successfully added to the model, but the ItemsControl UI is not reflecting the change.
I have tried refreshing the Items collection:
ItemsControlActivitiesActionItems.Items.Refresh();
But that has no effect. Any other ideas or advice would be appreciated.
UPDATE:
I can see that the Activity object that the new Note/ActionItem is being added to successfully is the Activity object PRIOR to the Undo operation. So even though the parent Activites ListBox control contains the latest Activity object and its synched properties are displaying correctly in the UI, the Notes and ActionItems ItemsControls are still pointing to the original Activity object before the Undo operation. So now the question is
"How do I force the 'child' ItemsControls to recognize the newly replaced object in the 'parent' (IsSynchronizedWithCurrentItem) ListBox control?"
So I was finally able to trace the problem back to the way I was cloning the objects. I was using the .MemberwiseClone method to make a shallow copy, and then turn that into a deep copy by manually copying all of the reference types. But somehow the WPF engine seemed to be holding onto a reference to the last object before the undo operation.
So I change my method of cloning to use a constructor that accepts the object to be copied as an argument. This is one of the other approaches suggested in the documentation:
There are numerous ways to implement a deep copy operation if the
shallow copy operation performed by the MemberwiseClone method does
not meet your needs. These include the following:
Call a class constructor of the object to be copied to create a second
object with property values taken from the first object. This assumes
that the values of an object are entirely defined by its class
constructor.
Call the MemberwiseClone method to create a shallow copy of an object,
and then assign new objects whose values are the same as the original
object to any properties or fields whose values are reference types.
The DeepCopy method in the example illustrates this approach.
Serialize the object to be deep copied, and then restore the
serialized data to a different object variable.
Use reflection with recursion to perform the deep copy operation.
I want my UI that is basically purely built on data-binding to refresh when I call ClearChange() or Refresh(RefreshMode.OverwriteChangesFromStore,obj). However it looks like the backing fields are used directly by DataAccess instead of the properties they are backing as the setters are not accessed yet the object's properties do indeed get reverted back.
Is there a way to tell DataAccess to use the properties instead so PropertyChanged is called within the setter?
Possibly any other solution? Nothing efficient comes to my mind
Having to work with a legacy silverlight application I ran into a strange piece of code. The viewmodel has a List dependency property as binding source for the grid. This DP has a default value, an other List that is used globally in the app. This is used to easily share entity data between different parts of the application.
DependencyProperty MyEntitiesProperty = DependencyProperty.Register("MyEntities", typeof(List<Entity>), typeof(...), new PropertyMetadata(Global.Entities));
Now, when the list is changed (on user actions), the global list is repopulated from database but MyEntities is never set explicitly. This does not work: the grid (the binding target) never changes. So its a wrong solution.
I speculate that the idea behind all this could have been been the following: if you have a DP with a given value and you never set a local value for it then the effective value of the DP will be the default value. If the 'underlying' default value is changed, the changes are reflected in the effective value.
If it worked, it was a nice way of sharing data between independent viewmodels without fiddling with property change events and such.
What is wrong here? Is it a big misunderstanding of how DPs work or the idea was ok and some implementation details were missed?
Please comment if something is not clear.
Well, taking also your comment into account, it is a big misunderstanding of how DPs work. Let me explain:
Setting a globally known list as the default value of MyEntities might not be a pattern I recommend, but is technically not faulty and can be done to share a list. MyEntities now holds a reference to this very list.
If you now replace the global list with a new list instance, the old instance does not cease to exist. Your property MyEntities still holds a reference to the old list. The value of a DP is only updated automatically if it is bound via Binding to either an ordinary property that is wired with the INotifyPropertyChanged mechanism or another DP.
Setting a default value happens neither via a Binding to an ordinary property nor via a Binding to another DP, it is just a plain old object reference.
I can think of several ways to correct the situation:
First solution
If the global list implements INotifyCollectionChanged (e.g. ObservableCollection, DependencyObjectCollection) you can - instead of creating a new list instance - just delete the old items from the list and add the new items. The views that have a reference to the list will perform an update as soon as they receive the associated CollectionChanged event.
Second solution
Make sure the Global.Entities list is available and always up-to-date as a public property (wired with INotifyPropertyChanged) on the DataContext of the root view. Now when you want a nested view somewhere deep down inside the UI tree to be connected to this Global.Entities list you can bind it to the root view's DataContext' public list property.
<MyRootView>
... nested views spread across multiple files ...
<MyNestedEntitiesListDisplay
MyEntities="{Binding
Path=DataConext.GlobalEntities,
RelativeSource={RelativeSource AncestorType=MyRootView}}"/>
I am using observable collections all around my applications. My problem is that when i use a popup window for editing those entities, my bound lists are getting changed when the user changes those corresponding fields in the window.
How could i simply freeze the observable changes norifications, and release them only when the entity is saved?
Thanks,
Oran
I think the issue is not with the collection, but with the entities themselves. ObservableCollection raises an event when an item is added or removed, not when a property of an item is changed. This part is handled by the INotifyPropertyChanged implemented by the item, so it's this notification you need to disable.
I suggest you have a look at the IEditableObject interface, which is designed for this kind of scenario. You can disable the notifications in the BeginEdit method, and reenable them in EndEdit and CancelEdit.
EDIT: Paul Stovell has a nice implementation of an IEditableObject wrapper here :
http://www.paulstovell.com/editable-object-adapter
You can use:
BoundPropertyOfViewModel = CollectionViewSource.GetDefaultView(AgentDeploymentDetail);
and bind to the view instead of binding directly to the ObservableCollection. This is the same object that allow you to filter/sort your output without touching the collection.
When you want to stop changes, use DeferRefresh(). When you are done, call Refresh().
WARNING
This will not pervent showing of changes in each item itself, only the list.
You could make a deep copy of the object you want to edit. This way, you can act on the copy while editing, without interfering with the original that remains in the list. Once you`re done editing, you can replace the original by the edited version or rollback.
All the anwers above are great. but i found a good and convinent prodedure to perform the desired in an efficient and clean way. It is based on performing a deep copy on a detached object, using Matthieu MEZIL entity cloner ( http://msmvps.com/blogs/matthieu/archive/2008/05/31/entity-cloner.aspx ).
For full details please check out the followings : Entity Framework Attach Exception After Clone
Thanks for all the great support...
This is a C# (v3.0) Winforms problem.
I have a big object that is associated with a BindingSource. When I have done with this object and the BindingSource, I want to remove the reference from the BindingSource so the object can be released. I used BindingSource.Clear(). But after that, in the memory profiler, I can still see the object alive and the only reference is from the BindingSource.lastCurrentItem.
My question is, how should I remove the reference from the BindingSource? Thanks.
What happens when you set BindingSource.DataSource = null?
BindingSource.Clear() clears all elements in the underlying list (BindingSource.List), but doesn't remove the reference to the data source. (Reference)