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.
I have a problem with MVVM pattern and binding collection. My ViewModel provides a collection to the View but to get this collection I use this:
public BindingList<Car> BindingListCars { get; set; }
public CarsVm()
{
BindingListVoiture = carServices.ListCars;
}
When I bind my View on this List it's as if I bind directly my View on the Model because they use the same reference. So when I edit one property of a Car, the model is directly edited without using carServices validation method.
What is the best solution to correct this problem ?
Do I have to expose a copy of my Model to my View to not edit directly my Model from the View?
Do I have to use BindingList in my Model and subsribe to ListChanged in my carServices to validate each change?
You should either perform the validation directly in the Car class itself or expose wrapper objects instead of exposing the "real" Car objects to the view.
The following sample code should give you the idea about what I mean:
//the "pure" model class:
public class Car
{
public string Model { get; set; }
}
public class CarService
{
public List<CarWrapper> ListCar()
{
List<Car> cars = new List<Car>(); //get your Car objects...
return cars.Select(c => new CarWrapper(c, this)).ToList();
}
public bool Validate()
{
//
return true;
}
}
public class CarWrapper
{
private readonly Car _model;
CarService _service;
public CarWrapper(Car model, CarService service)
{
_model = model;
_service = service;
}
//create a wrapper property for each property of the Car model:
public string Model
{
get { return _model.Model; }
set
{
if(_service.Validate())
_model.Model = value;
}
}
}
Obviously if you expose an IEnumerable<Car> from your view model for the view to bind, you are effectively bypassing any validation that is dedined outside of the Car class if the view is able to set any properties of the Car class.
Thanks for your answer mm8,
With this solution I have to create one wrapper per class which need outside validation. It add work and during refactoring we have to edit the class and the Wrapper.
What do you think about this solution :
I put my list of vehicle in a binding list
My service subscribe to ListChanged event of this list
My service implement INotifyDataErrorInfo
For each modification in this list validation is executed
If there is an error ErrorsChanged event is raised
The view model subsribe to this event and retrieve error Data.
The view model subsribe to this event and retrieve error Data.
For example :
My services implementation :
public class VehicleServices : INotifyDataErrorInfo
{
private BindingList<Vehicle> _bindingListCar
public BindingList<Vehicle> BindingListCar
{
get return _bindingListCar;
}
private readonly Dictionary<string, ICollection<string>>
_validationErrors = new Dictionary<string, ICollection<string>>();
//INotifyDataErrorInfo implementation
public IEnumerable GetErrors(string propertyName)
public bool HasErrors
private void RaiseErrorsChanged(string propertyName)
public VehicleServices()
{
_bindingListCar = GetVehicles();
_bindingListCar.ListChanged += BindingListVehicleChanged;
}
private void BindingListVehicleChanged(object sender, ListChangedEventArgs e)
{
//Only modification is managed
if (e.ListChangedType != ListChangedType.ItemChanged) return;
switch(e.PropertyDescriptor.Name)
//Validate each property
//if there is ErrorsChanged is raised
}
}
And my ViewModel
public class CarVm : BindableBase
{
private ICollection<string> _errors;
public ICollection<string> Error
{
get
{
return _errors;
}
set
{
SetProperty(ref _errors, value);
}
}
private VehicleServices _carServices;
public BindingList<Vehicle> BindingListCar { get; set; }
public CarVm(VehicleServices carServices)
{
_carServices = carServices;
BindingListCar = new BindingList<Vehicle>(_carServices.BindingListCar);
_carServices.ErrorsChanged += _carServices_ErrorsChanged;
}
private void _carServices_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
Error = _carServices.ValidationErrors[e.PropertyName];
}
}
Do you think this is a good practice ?
I have had a challenge finding anything on the following situation. Let's say I have a record called DatabaseRecord which is a POJO with several fields:
public class DatabaseRecord {
private Long recordId;
private StringProperty foreignKeyId;
private StringProperty otherValuesForMainRecord....
... setters/getters
public class LookUpDataValue {
private String recordId;
private String descriptiveText;
private String otherValues.....
....
I have a combo box which displays the selectable values for the foreignKeyId value something like this:
#FXML
ComboBox<LookUpDataValue> combobox;
combobox.setCellFactory
(new Callback<ListView<LookUpDataValue>, ListCell<LookUpDataValue>>() {
#Override
public ListCell<LookUpDataValue> call(ListView<LookUpDataValue> p) {
ListCell<LookUpDataValue> cell = new ListCell<LookUpDataValue>() {
#Override
protected void updateItem(LookUpDataValue item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText("");
} else {
setText(item.getDescriptiveText());
}
}
};
return cell;
}
});
I would like to Bidirectionally bind the POJO DatabaseRecord.foreignKeyIdProperty to the combobox.valueProperty() - but this doesn't work since one is a string and one is a LookUpDataValue object.
I would like this combobox to be set correctly when the records are loaded, and conversely update the record when it's changed.
I would appreciate any pointers on where I can find an example of this...
I was not able to change my datamodel so I found the following solution which works.
// Handle ComboBox event.
myComboBox.setOnAction((event) -> {
LookUpDataValue selected = myComboBox.getSelectionModel().getSelectedItem();
if(selected != null){
dto.setForeignKeyId(selected.getId());
}
});
This works well. Thanks for your comments.
I'm following MVVM pattern. I have a listview control which has multiple checkboxes.
my viewmodel has collection of Student which is bounded to listview control.
public ObservableCollection<Student> students{ get; private set; }
private ObservableCollection<Student> _displays { get; set; }
viewmodel doesn't know anything about the view so it doesn't access to the listview control
I tried by defining the Student class by below
public class Student: INotifyPropertyChanged
{
public string Name{ get; set; }
public string class { get; set; }
//Provide change-notification for IsSelected
private bool _fIsSelected = false;
public bool IsSelected
{
get { return _fIsSelected; }
set
{
_fIsSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
now i want to perform some action in viewmodel when user select/deselect the checkbox.
how can achieve this? is it correct way to define above class?
Your Student class knows when its values change, so you just need to add a PropertyChanged handler to your collection of Student objects in the view model. In this way, your view model can get notified when the values of the Student classes change. Try something like this:
foreach (Student student in students)
{
student.PropertyChanged += Item_PropertyChanged;
}
...
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
// The IsSelected property was changed
}
}
Note that you will have to add a handler to every item in the collection. You could do that in a loop, as shown above, or by extending the ObservableCollection<T> class and overriding the Add, Insert andRemove methods and the constructors.
UPDATE >>>
Expanding on my final paragraph from above, you can extend the ObservableCollection<T> class and override the Add and Remove methods and the constructors... at its most basic, you could do something like this:
public class Students : ObservableCollection<Student>
{
public Students(IEnumerable<Student> students)
{
foreach (Student student in students) Add(student);
}
public new void Add(T item)
{
item.PropertyChanged += Item_PropertyChanged;
base.Add(item);
}
public new bool Remove(T item)
{
item.PropertyChanged -= Item_PropertyChanged;
return base.Remove(item);
}
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Notify of all item property changes
NotifyPropertyChanged(e.PropertyName);
}
// Implement the INotifyPropertyChanged interface properly here
}
The benefit of this method is that you can then just attach one handler to the collection, rather than having to manually attach them to each item:
Students.PropertyChanged += Item_PropertyChanged;
Hi I produced a windows form. The goal is to book seats on a airplane but now i'm finding quite distressing the path that I choose - when the passenger chooses the seat graphically by pressing a button the button's color is being changed but now I need to add a piece of code that could help me save this information and associate it with the passenger.
Regards
private void button4_Click(object sender, EventArgs e)
{
this.F16.BackColor = Color.LightBlue;
this.F16.ForeColor = Color.Red;
}
If I understand right, then just create a new class Passenger, where you keep all information about passenger and choosen places by him
public class Passenger
{
public string Name { get; set; }
public string place { get; set; } // or list of places
public Passenger(String name)
{
this.Name = name;
}
public void savePlace(string newPlace)
{
this.place = newPlace;
}
}
Then in your form create a variable
private Passenger currentPassenger;
And inside of your function save a choosen place
private void button4_Click(object sender, EventArgs e)
{
this.currentPassenge.SavePlace(this.F16.name)//or just "F16"
this.F16.BackColor = Color.LightBlue;
this.F16.ForeColor = Color.Red;
}
if your have own button for every place, then better assign a place's data in .Tag property
this.button4.Tag = "F16"
if so then you need only one handler for all buttons
private void PlaceButtons_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
this.currentPassenger.savePlace((string)btn.Tag);
this.F16.BackColor = Color.LightBlue;
this.F16.ForeColor = Color.Red;
}
and ofcourse in constructor of Form add event handler to all place's buttons
this.button1.Click +=new EventHandler(PlaceButtons_Click);
this.button2.Click +=new EventHandler(PlaceButtons_Click);
this.button4.Click +=new EventHandler(PlaceButtons_Click);
Or if you want associate in other way places with passenger then create Place class
public class Place
{
public string Code{ get; set; }//code of place "F16"
public string passenger { get; set; } //Name of passenger
public Place(String passengerName)
{
this.passenger = passengerName;
}
public void SavePassenger(string newpassenger)
{
this.passenger = newpassenger;
}
}
And do the same things in event handler PlaceButtons_Click, but save passenger name in Place class. In form create a list of places or hashtable (Place; Passenger) or something else independing on using of application.