I've implemented deep cloning of ObservableCollection in order to reset items to It's original state in editable Datagrid, via cancel button.
For this I have two collections - one ObservableCollection to bind Datagrid to It, and cloned List to re-initialize ObservableCollection to It's original state when needed.
My code works only first time I hit a cancel button, after that my cloned List has changes in It too.
Provided code is an example (mine is a bit longer), but It's 100% same as mine:
Model, which implements ICloneable:
public class EmployeeModel : ICloneable
{
public object Clone()
{
return MemberwiseClone();
}
public string NAME
{
get { return _name; }
set
{
if (_name != value)
{
CHANGE = true;
_name = value;
}
}
}
private string _name;
public string SURNAME
{
get { return _surname; }
set
{
if (_surname != value)
{
CHANGE = true;
_surname = value;
}
}
}
private string _surname;
///<summary>Property for tracking changes in model</summary>
public bool CHANGE { get; set; }
}
Viewmodel:
public ViewModel() : Base //Implements InotifyPropertyChanged
{
public ViewModel()
{
Task.Run(()=> GetData());
}
public ObservableCollection<EmployeeModel> Employees
{
get { return _employees; }
set { _employees = value; OnPropertyChanged();}
}
private ObservableCollection<EmployeeModel> _employees;
public List<EmployeeModel> Copy_employees
{
get { return _copy_employees; }
set { _copy_employees = value; OnPropertyChanged();}
}
private List<EmployeeModel> _copy_employees;
//Fetch data from DB
private async Task Get_data()
{
//Returns new ObservableCollection of type Employee
Employees = await _procedures.Get_employees();
if (Employees != null) //Now make a deep copy of Collection
{
Copy_employees = new List<EmployeeModel>();
Copy_employees = Employees.Select(s => (EmployeeModel)s.Clone()).ToList();
}
}
//My Command for canceling changes (reseting DataGrid)
//CanExecute happens, when model is changed - tracking via CHANGE property of EmployeeModel
public void Cancel_Execute(object parameter)
{
Employees.Clear(); //Tried with re-initializing too, but same result
foreach (var item in Copy_employees)// Reset binded ObservableCollection with old items
{
Employees.Add(item);
}
//Check if copied List really hasn't got any changes
foreach (EmployeeModel item in Copy_employees)
{
Console.WriteLine("Changes are " + item.CHANGES.ToString());
}
}
}
Output of cancel command:
1.) First time I hit cancel button:
// Changes are False
Every next time:
// Changes are True
So, as I see It from Console, my copied List get's updated when ObservableColection get's updated, even if It's not binded to DataGrid.
And It updates only a property which I changed, so List reflects ObservableCollection items.
How can I keep my original items of List<Employee>, and copy those into binded ObservableCollection anytime ?
When you return values, you do not return them, but write backing item references to the editable collection.
As a result, you have the same instances in both collections.
In the simplest case, when you return them, you also need to clone.
public void Cancel_Execute(object parameter)
{
Employees.Clear(); //Tried with re-initializing too, but same result
foreach (var item in Copy_employees)// Reset binded ObservableCollection with old items
{
Employees.Add((EmployeeModel)item.Clone());
}
//Check if copied List really hasn't got any changes
foreach (EmployeeModel item in Copy_employees)
{
Console.WriteLine("Changes are " + item.CHANGES.ToString());
}
}
Not relevant to the question, but I still advise you to use a slightly more user-friendly interface for cloneable:
public interface ICloneable<T> : ICloneable
{
new T Clone();
}
I'm fighting it the second day and I'm just fed up.
I'm getting weird exceptions connected with my UI.
First things first.
My model looks basically like that:
Base class:
public class DbItem: ObservableModel
{
public virtual Document ParentDocument { get; set; }
Guid id;
public virtual Guid Id
{
get { return id; }
set
{
if (id != value)
{
id = value;
NotifyPropertyChanged();
}
}
}
string name = string.Empty;
public virtual string Name
{
get { return name; }
set
{
if (value == null || name != value)
{
name = value;
NotifyPropertyChanged();
}
}
}
}
Next we have PeriodBase class:
public enum PeriodType
{
Year,
Sheet
}
public abstract class PeriodBase : DbItem
{
public virtual Period ParentPeriod { get; set; }
public virtual PeriodType PeriodType { get; set; }
}
There are some more properties, but I just deleted them here for clarity.
Next, we have Period class that inherits from PeriodBase:
public class Period : PeriodBase
{
IList<PeriodBase> periods = new ObservableCollection<PeriodBase>();
public virtual IList<PeriodBase> Periods
{
get { return periods; }
set
{
if (periods != value)
{
periods = value;
NotifyPropertyChanged();
}
}
}
}
Now, Period can have other periods and Sheets (which also inherites from PeriodBase):
public class Sheet : PeriodBase
{
DateTimeOffset startDate;
public override DateTimeOffset StartDate
{
get { return startDate; }
set
{
if (startDate != value)
{
startDate = value;
NotifyPropertyChanged();
}
}
}
DateTimeOffset endDate;
public override DateTimeOffset EndDate
{
get { return endDate; }
set
{
if (endDate != value)
{
endDate = value;
NotifyPropertyChanged();
}
}
}
}
And finally we have document class, that is made up of Periods:
public class Document: DbItem
{
IList<Period> periods = new ObservableCollection<Period>();
public virtual IList<Period> Periods
{
get { return periods; }
set
{
if (periods != value)
{
periods = value;
NotifyPropertyChanged();
}
}
}
}
As you may guess, I get a tree hierarchy like that:
- Document
- Period 1
- Sheet 1
My bindings look like this:
public class DocumentMap : DbItemMap<Document>
{
public DocumentMap()
{
Table("documents");
HasMany(x => x.Periods).ForeignKeyConstraintName("ParentDocument_id");
}
}
public class PeriodBaseMap: DbItemMap<PeriodBase>
{
public PeriodBaseMap()
{
UseUnionSubclassForInheritanceMapping();
References(x => x.ParentPeriod);
Map(x => x.Name).Not.Nullable();
Map(x => x.PeriodType).CustomType<PeriodType>();
}
}
public class PeriodMap : SubclassMap<Period>
{
public PeriodMap()
{
Table("periods");
Abstract();
References(x => x.ParentDocument);
HasMany(x => x.Periods).Inverse().Not.LazyLoad();
}
}
public class SheetMap : SubclassMap<Sheet>
{
public SheetMap()
{
Table("sheets");
Abstract();
Map(x => x.StartDate);
Map(x => x.EndDate);
}
}
For now, I just do eager loading everywhere. Just for simplicity.
Now WPF. This is how I create my TreeView (I'm using syncfusion controls):
<sf:TreeViewAdv>
<sf:TreeViewItemAdv
Header="Document"
LeftImageSource="../Resources/database.png"
ItemsSource="{Binding Periods}"
IsExpanded="True"
>
<sf:TreeViewItemAdv.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Periods}"> <!-- Period -->
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/> <!-- Sheet -->
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</sf:TreeViewItemAdv.ItemTemplate>
</sf:TreeViewItemAdv>
</sf:TreeViewAdv>
And everything works until I save the records. It's just simple SaveAsync's in one transaction.
Everything gets saved but then I get a weird error. Application crashes with message: Cannot cast TreeViewItemAdv to PeriodBase.
What the heck? I can't even find the place when it's really throws.
This is stacktrace from exception info:
in NHibernate.Collection.Generic.PersistentGenericBag`1.System.Collections.IList.IndexOf(Object value)
in System.Windows.Data.ListCollectionView.InternalIndexOf(Object item)
in Syncfusion.Windows.Tools.Controls.TreeViewItemAdv.Initialize(FrameworkTemplate template)
in Syncfusion.Windows.Tools.Controls.TreeViewItemAdv.TreeViewItemAdv_Loaded(Object sender, RoutedEventArgs e)
in System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
in System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
in System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
in System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)
in MS.Internal.LoadedOrUnloadedOperation.DoWork()
in System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
in System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
in System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
in System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
in System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
in System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
What's important, I get the same error after I start the application and load the document and click on the expander in treeview to expand Period. But everything works fine when I run the app for the first time, until I save the document.
What can be the problem?
In reply to Mark Feldman's post
I decided to reply in an answer as this is too long to comment. This is my first meeting with ORM, so I may have some wrong thoughts about this. I have just one model in my solution. Normally (using SQL) it would work. I would take an object, INSERT it into DB, and the other way also.
So I did the same way here. I just have one business model which has some simple business rules. It is used in ViewModels, and it's stored in db. Is it bad solution? Should I have another model and somewhat break DRY principle?
In my head it was suppose to work like this: User clicks "Create new Sheet". Here you are (this is part of my ViewModel -> method that is called from command):
void CreateNewSheetInActiveDocument()
{
Sheet sh = ActiveDocument.CreateItem<Sheet>();
ActiveDocument.LastPeriod.Periods.Add(sh);
}
This is more like pseudocode but it keeps the idea. Active document creates my sheet. This is done so because document signs to PropertyChanged event just to know if it was modified. Periods is ObservableCollection, so that I can react to adding and removing elements. Thanks to that period can set parentPeriod for my sheet automatically.
And then user saves it to db:
async Task SaveDocument(Document doc)
{
foreach(var item in doc.ModifiedItems)
db.SaveOrUpdate(item);
}
ModifiedItems is simply just a dictionary that keeps items that were modified. Thanks to this I don't have to save the whole document, just modified items.
So as far as I understand you this is not the way it should be. So what would be the PROPER way to do that? Or maybe ORM is not suitable here?
Unless there have been major changes to NHibernate in the years since I've used it you can't just derive your model classes from ObservableModel and expect it to work. It appears that your reasoning for this is to give INPC to your DB models, which some would argue isn't good separation of concerns and suggests that your view model layer hasn't been designed properly.
That said, if you really are adamant about doing it then instead of deriving your entities from ObservableModel try using something like Castle Dynamic Proxy to inject INPC into your entities when NHibernate first creates them. Ayende Rahien's post NHibernate & INotifyPropertyChanged shows how to do this and also provides the code you'll need.
The next problem you'll face is the issue of collections. Again, you can't just assign an ObservableCollection<T> to an IList<T> property and expect it to work, NHibernate replaces the entire list when it deserializes collections back in rather than using add/remove on an existing collection that you've already assigned. It's possible to replace the list with an ObserveableCollection<T> after its been loaded, but if you do that then NHibernate will think the entire list has changed, irrespective of whether it has or not, and serialize the whole thing back out again. You'll get away with it at first, but pretty soon the performance hit is going to start to hurt.
To work around that problem you're going to have to use a convention so that NHibernate creates collection entities that support INotifyCollectionChanged. Unfortunately the page where I originally read about this has long since disappeared, so I'll have to just post the code here (regrettably without attribution). I've only used conventions with NHibernate Fluent, so I'll leave you to find out how to apply them in your own case, but here's what you need...
public class ObservableBagConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
Type collectionType = typeof(ObservableBagType<>)
.MakeGenericType(instance.ChildType);
instance.CollectionType(collectionType);
instance.LazyLoad();
}
}
public class ObservableBagType<T> : CollectionType, IUserCollectionType
{
public ObservableBagType(string role, string foreignKeyPropertyName, bool isEmbeddedInXML)
: base(role, foreignKeyPropertyName, isEmbeddedInXML)
{
}
public ObservableBagType()
: base(string.Empty, string.Empty, false)
{
}
public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
{
return new PersistentObservableGenericBag<T>(session);
}
public override IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister, object key)
{
return new PersistentObservableGenericBag<T>(session);
}
public override IPersistentCollection Wrap(ISessionImplementor session, object collection)
{
return new PersistentObservableGenericBag<T>(session, (ICollection<T>)collection);
}
public IEnumerable GetElements(object collection)
{
return ((IEnumerable)collection);
}
public bool Contains(object collection, object entity)
{
return ((ICollection<T>)collection).Contains((T)entity);
}
protected override void Clear(object collection)
{
((IList)collection).Clear();
}
public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
{
var result = (ICollection<T>)target;
result.Clear();
foreach (var item in ((IEnumerable)original))
{
if (copyCache.Contains(item))
result.Add((T)copyCache[item]);
else
result.Add((T)item);
}
return result;
}
public override object Instantiate(int anticipatedSize)
{
return new ObservableCollection<T>();
}
public override Type ReturnedClass
{
get
{
return typeof(PersistentObservableGenericBag<T>);
}
}
}
That's the code for the convention, you use it with this collection class:
public class PersistentObservableGenericBag<T> : PersistentGenericBag<T>, INotifyCollectionChanged,
INotifyPropertyChanged, IList<T>
{
private NotifyCollectionChangedEventHandler _collectionChanged;
private PropertyChangedEventHandler _propertyChanged;
public PersistentObservableGenericBag(ISessionImplementor sessionImplementor)
: base(sessionImplementor)
{
}
public PersistentObservableGenericBag(ISessionImplementor sessionImplementor, ICollection<T> coll)
: base(sessionImplementor, coll)
{
CaptureEventHandlers(coll);
}
public PersistentObservableGenericBag()
{
}
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add
{
Initialize(false);
_collectionChanged += value;
}
remove { _collectionChanged -= value; }
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged
{
add
{
Initialize(false);
_propertyChanged += value;
}
remove { _propertyChanged += value; }
}
#endregion
public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize)
{
base.BeforeInitialize(persister, anticipatedSize);
CaptureEventHandlers(InternalBag);
}
private void CaptureEventHandlers(ICollection<T> coll)
{
var notificableCollection = coll as INotifyCollectionChanged;
var propertyNotificableColl = coll as INotifyPropertyChanged;
if (notificableCollection != null)
notificableCollection.CollectionChanged += OnCollectionChanged;
if (propertyNotificableColl != null)
propertyNotificableColl.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler changed = _propertyChanged;
if (changed != null) changed(this, e);
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler changed = _collectionChanged;
if (changed != null) changed(this, e);
}
}
And that's it! Now NHibernate will deserialize your collections as type PersistentObservableGenericBag<T>.
So that's how you inject INPC into entities at runtime, but there are a couple of ways to accomplish what you need without actually having to do that. Apart from being easier to implement they also don't require the use of reflection, which is a factor if you ever need to migrate your code to something that doesn't allow it (e.g. Xamarin.iOS). Adding basic INPC can be achieved by simply adding ProprtyChanged.Fody which will add it to your class properties IL automatically at build time. For change collection you're better off keeping your collections as type IList<T>, representing them with classes of type ObserveableCollection<T> in your view models and then just writing a bit of code, or a helper function, to keep the two synchronized.
UPDATE: I managed to track down the original project where I got that code, it's part of Fabio Maulo's uNhAddIns project.
After Mark Feldman's changes, the error still occures. But when I changed the tree control to standard one, the problem went away. That means there is an error in Syncfusion control. I have reported it.
We all like how easy it is to bind with WPF. Now I am back working with Winforms and I am looking for a nice way to bind my grid to a List of Checkable of BusinessObject (I am sticking with BindingList for Winforms). So I am essentially just adding a checkable to my business object.
I am using a grid as there will be multiple columns where the user would edit (in this scenario Name and Description on the business object) - as well as adding new objects to the grid and removing from it. Checked list box does not fit for this purpose as I want to edit columns.
For this I am using .NET 4.
I basically want to reduce the amount of UI code in the scenario so I am using a view model based approach which will populate the list. I want the user to be able to check a box alongside each of the business object properties.
Sure I can use inheritance, but if I want to apply the same mechanism against a lot of business objects (having lots of different screens where you check items in a list for the different business objects). Maybe this would be the way to go - but I have my doubts.
Now depending upon the choice of grid - I am using Infragistics - the functionality would hopefully be pretty similar conceptually.
I thought about wrapping the business object up in a Checkable generic class:
using System;
using System.Collections.Generic;
public class Checkable<T> : ModelBase
{
public Checkable(T value)
{
_value = value;
}
private T _value;
public T Value
{
get
{
return _value;
}
set
{
if (!EqualityComparer<T>.Default.Equals(_value, value))
{
_value = value;
OnPropertyChanged("Value");
}
}
}
private bool _checked;
public bool Checked
{
get { return _checked; }
set
{
if (_checked != value)
{
_checked = value;
OnPropertyChanged("Checked");
}
}
}
}
I have made up a business object for this scenario:
public class BusinessObject : ModelBase
{
public BusinessObject()
{
}
public BusinessObject(RepairType repairType)
{
_name = repairType.Name;
_id = repairType.Id;
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private string _description;
public string Description
{
get { return _description; }
set
{
if (description != value)
{
description = value;
OnPropertyChanged("Description");
}
}
}
private int _id;
public int Id
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
OnPropertyChanged("Id");
}
}
}
}
Where ModelBase just implements the INotifyPropertyChanged:
public abstract class ModelBase : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T field, T value, string propertyName = null)
{
if (object.Equals(field, value)) { return false; }
field = value;
OnPropertyChanged(propertyName);
return true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (disposing)
{
PropertyChanged = null;
}
}
}
So potentially for my grid datasource I would define:
// in view model
var datasource = new BindingList<Checkable<BusinessObject>>();
... populate list
grid.DataSource = viewmodel.DataSource;
So of course my scenario fails at the minute as Value is the BusinessObject reference which has the properties I want to bind to, and Checked is the property for a checkbox which I also want to bind to.
I am trying to kick start the old grey matter with some ideas on this. I don't really like writing code to define grid columns. However, the Infragistics grid has been ok for data binding directly to the BusinessObject at design time. Its possible to add an unbound column (checkbox for my scenario) and handle the checking/unchecking of items manually (which I might potentially have to do).
I am wondering if I am missing any neat tricks with Winform binding of late having missed out with Linq and Entity Framework when they appeared many years ago.
I have a number of collection bound to the application main window controls shown in a
simplified form below. There are a number of other elements in the view model
(Ommited for Clarity) which all update and work as expected.
I require to edit a collection's element in another window, with the edited data back in the origonal collection.
/// Example of the Collection and Properties
ObservableCollection<MyData> _MyCollection = new ObservableCollection<MyData>();
public ObservableCollection<MyData> MyCollection { get { return _MyCollection; } }
public class MyData : INotifyPropertyChanged
{
private bool cb_checked;
public string Param1 { get; set; }
public string Param2 { get; set; }
public bool myCheck
{
get { return cb_checked; }
set
{
if (cb_checked == value) return;
cb_checked = value;
RaisePropertyChanged("Checked");
}
}
}
My problem is how do I pass an item of a collection to a new window for editing.
My intal thoughts were to pass the item in the constructor of the window
Dialog.Edit window = new Dialog.Edit(_MyCollection[2] );
window.Owner = this;
window.Show();
I also tried this as I have read I cant use indexed references
var tmp = _MyCollection[2];
Dialog.Edit window = new Dialog.Edit( tmp);
window.Owner = this;
window.Show();
but this does not work and I get null exceptions whe trying to access elements.
If I need to pass the complete collection this is also ok as they are all quite small i.e. < 50 items.
I must be going about this in the wrong way, could someone please explain how to do this
correctly please.
Many Thanks
Sarah
I have a viewmodel containing two CollectionViews defined.
One I am using for navigation and data entry/edit.
Another I want to use for filtering purpose and show the filteration in some Listview on the form.
I don't want the main view(used for DataEntry purpose) to get affected while I applying filteration on observablecollection.
Thanks in Advance!
As long as you're using separate collection views, changing one won't affect the other. That is the point of collection views - they're independent views on the same collection.
ok, Got it! and went ahead with the same idea. But when I did so, I get Error = "The calling thread cannot access this object because a different thread owns it.". Hence my filteration doesn't work.. Following is the code-
public ICollectionView Clients { get; set; } //Used for Data-navigation/modification
public ListCollectionView CodeView { get; set; } // to be used for filteration purpose on form.
string searchText = String.Empty;
public string CompanyCodeSearch
{
get { return searchText; }
set
{
try
{
searchText = value;
OnPropertyChanged("CompanyCodeSearch");
CodeView.Filter = new Predicate<object>(cmFilterData);
}
catch (Exception ex)
{
}
}
}
private bool cmFilterData(object item)
{
bool _filteredData = false;
try
{
var value = (item as cntClient);
if (value == null || value.CompanyCode == null)
return false;
_filteredData = value.CompanyCode.StartsWith(this.CompanyCodeSearch);
return _filteredData;
}
catch (Exception ex)
{
return false;
}
}