I am having a problem with DomainContext.RejectChanges() and reflecting the rollback in the UI. Here is my scenario.
I have a Model (Entity) generated for use with RIA services (I'll call it Foo)
I have a ViewModel that wraps Foo and extends it (I'll call it FooViewModel)
I have a View that is using Binding to display and update data using the FooViewModel
I have an "outer" ViewModel that holds an ObservableCollection of FooViewModels
The "outer" View has a list box bound to the ObservableCollection
So essentially there is a listbox of FooViewModels on one screen...when you select an item a childwindow is displayed to edit that particular FooViewModel. The FooViewModel is serving both the listbox and the childwindow.
Editing works just fine. A change in the childwindow reflects in the listbox immediately because I am calling RaisePropertyChanged() when the viewmodel properties are updated.
However, If I perform a DomainContext.RejectChanges()...the underlying entity gets rolled back (all changes reverted as expected)...however the FooViewModel isn't aware that this change has occurred and thus the UI isn't updated. If I reselect the item in the listbox on the first screen, the childwindow is displayed with the rolled back changes (which is what I want). The listbox still isn't updated though.
When I reject changes, if I kludge a RaiseProperyChanged() for the field that I changed...the UI listbox does update.
How do I get the UI to update when the underlying entity is rejected?? And how do I do it without tracking what properties of the viewmodel were rolledback? There has to be an easy way to accomplish this that I am just missing.
Something you could try is use the PropertyChanged event on the underlying entity Foo to trigger a RaisePropertyChanged pass on the FooViewModel properties.
so making some assumptions (so this code make sense):
You have a private variables in your FooViewModel
private Foo _foo;
private DomainContext _context;
You have a method on your FooViewModel that is calling RejectChanges() on your domain context.
Like so:
public void RejectChanges()
{
_context.RejectChanges();
}
We have a method that raises the PropertyChanged event on our FooViewModel
Like so:
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(propertyName);
}
Ok, now we have that established, lets have a look at exactly what happens when you call RejectChanges() on a domain context.
When you call RejectChanges() this bubbles down through the DomainContext to its EntityContainer, then to each EntitySet in that container and then to each Entity in the set.
Once there (and in the EntitySet), it reapplies the original values if there was any, removes the entity if it was added, or adds it if it was deleted. If there was changes to the values, then it applies them back to the properties.
So theoretically, all the RaisePropertyChanged(), that are generated in the entity properties, should be triggered.
NOTE: I haven't actually tested this. If this isn't the case, then none of this works :P
So we can hook into PropertyChanged event of the Foo entity, and raise the PropertyChanged event on our FooViewModel.
so our RejectChanges() method might look like this:
public void RejectChanges()
{
Func<object, PropertyChangedEventArgs> handler = (sender, e) =>
{
RaisePropertyChanged(e.PropertyName);
};
_foo.PropertyChanged += handler;
_context.RejectChanges();
_foo.PropertyChanged -= handler;
}
So we hook up an event handler to our Foo entity, which calls the FooViewModel.RaisePropertyChanged method with the property name that is changing on the Foo entity.
Then we reject changes (which triggers the property changes),
then we unhook the event handler.
Pretty long winded, but I hope this helps :)
I presume that the call to DomainContext.RejectChanges() is happening within the ViewModel as you probably bound that to some command or method called from the parent ViewModel. Since all your bindings to the data is done on the ViewModel properties you will have to raise the property change event on them when you directly manipulate the model outside of those properties. Which you probably doing already.
public void RejectChanges()
{
DomainContext.RejectChanges();
RaisePropertyChangeOnAll();
}
How you implement RaisePropertyChangeOnAll() can be done simply with a list of RaisePropertyChange("...") for each property or you could do it through reflection (if Silverlight permissions allow, not too sure about it) by adding an Attribute on each property you want to raise. Find all the properties that are tagged with it and call RaisePropertyChanged on the MemberInfo.Name value.
[Raiseable]
public string SomeValue
{
...
}
Just an idea but may not be a perfect solution.
Related
I have a ViewModel that it has some events to notify actions to anothers viewModels.
I have few events, but I will ask about one them, that it is this:
public event MyEventEventHandler MyEvent;
private void OnMyEvent(MyType param)
{
MyEventEvent?.Invoke(param);
}
In a intermediate ViewModel, I relation a second view model that has a method to subscribe to this event. I do in this way:
MainViewModel myMainViewmodel = new MyViewModel();
SecondViewModel mySecondViewModel = new SecondViewModel();
myMainViewmodel.MyEvent += mySecondViewmodel.handleEvent;
In this case, it is to open a dialog, so the main view model is the dialog and the second view model is which calls to the dialog, so when I finish to open the dialog, how the second view model still is subcribed to the main ViewModel, the main ViewModel would not be recollected by the garbage collector.
My doubt is where I should unsubscribe the event, in the disponse of the main ViewModel or the finally block in the intermediate view model?
I was thinking if there would be someway to access to the collection of subcribers in the main ViewModel, so in the dipose I could do something like MyEvent.clear() or something like that to unsubcribe all the events, instead to unsubscribe one by one all of the in the finally block in the intermediate ViewModel.
In general, I would like to know the best way to unsubscribe events when I related two ViewModels.
Thanks.
You shouldn't make a type IDisposable only for one purpose of being able to unsubscribe from events. IDisposable means, a type contains unmanaged resources or instances of other IDisposable types.
If your view-model already implements IDisposable, you can of course unsubscribe from events there.
If not, you can create a CleanUp (or similar) method and unsubscribe there.
There's a very neat solution I use for such things. I call them one-shot event handlers (or auto-unsubscribing event handlers). This is where the new feature of C#7 "local functions" helps.
void IntermediateViewModel_CreateViewModels()
{
MainViewModel myMainViewmodel = new MyViewModel();
SecondViewModel mySecondViewModel = new SecondViewModel();
myMainViewmodel.MyEvent += LocalEventHandler;
void LocalEventHandler(object sender, EventArgs e)
{
// unsubscribe --> no memory leaks!
myMainViewmodel.MyEvent -= LocalEventHandler;
mySecondViewmodel.handleEvent(sender, e);
}
}
With such approach, when the MainViewModel.MyEvent fires, the local function LocalEventHandler is executed as event handler. You can unsubscribe from the event right away in that local function, so that there's no reference anymore (no memory leak, the view-model can be GCed).
From your use case I understood that you want to create a dialog view-model, display it using a view, and then immediately get rid of that view-model. Then, my sample perfectly fits to your requirements.
I work on a WPF project build in MVVM architecture.
My ViewModel class contains ObservableCollection of SentenceVM class which is also a ViewModel class (implements INotifyPropertyChanged).
This ObservableCollection bound to a DataGrid and I would like to allow adding new records to the collection via the DataGrid build in function.
ObservableCollection<SentenceVM> observe = new ObservableCollection<SentenceVM>()
The problem is that the CollectionChanged event fires at the beginning of the adding row procedure. Thus, I don't know when to refer to the database to submit the new data.
I need this event to be fired when the user has finished adding the new data, not at the beginning.
I know it could be done by generating a command which executes on END inserting or on Enter key but i'm looking for a practical way to do so with this ObservableCollection.
Thank you !
I found out there is no way to do so via ObservableCollection. The only way to do so in practical way is to define an 'EventToCommand' trigger which executes on CellEditEnding event and execute the bounded command, like that:
1) Define the trigger class which inherits from the TriggerAction class and define the invoke method:
public class EventToCommandTrigger : TriggerAction<FrameworkElement>
2) Define the ICommand (dependency property) to bound to:
public ICommand CommandToExecute
{
get { return (ICommand)GetValue(CommandToExecuteProperty); }
set { SetValue(CommandToExecuteProperty, value); }
}
public static readonly DependencyProperty CommandToExecuteProperty =
DependencyProperty.Register("CommandToExecute", typeof(ICommand), typeof(EventToCommandTrigger), new FrameworkPropertyMetadata(null));
3) Implement the abstract Invoke method like that:
protected override void Invoke(object parameter)
{
if (CommandToExecute == null)
return;
if (CommandToExecute.CanExecute(parameter))
CommandToExecute.Execute(parameter);
}
4) Then in the xaml code, connect the CellEditEnding event to the above trigger, like that:
<i:Interaction.Triggers>
<i:EventTrigger EventName="CellEditEnding">
<triggers:EventToCommandTrigger CommandToExecute="{Binding Path=DeleteCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
The problem is that the CollectionChanged event fires at the beginning of the adding row procedure. Thus, I don't know when to refer to the database to submit the new data.
In my opinion, there's nothing wrong with inserting the data into the database inside the CollectionChanged event. In fact, the NotifyCollectionChangedEventArgs gives you everything you need in order to perform this action.
Consider looking at e.NewItems, and e.OldItems. The NewItems is the data that is being inserted into the collection, you can simply iterate through this list and act accordingly.
Okay, I have an ItemsControl in a XAML file that binds to an ObservableCollection. The ObservableCollection is found on a view-model class (let's call this class ViewModelA), and each item in the ObservableCollection is an instance of another view-model class (let's call the class ViewModelB).
There is a property on ViewModelA, that, when changed, will indirectly change the values of properties found in many instances of the ViewModelB class. In other words, it doesn't go straight to ViewModelB and set its properties, thus causing an INotifyPropertyChange call, but rather goes down to the model, sets some property in my model, and that change in my model affects what ViewModelB should be showing the view.
How can I notify the view that something in ViewModelB has changed?
You should be able to tell the View that the collection has changed, and in turn, trigger it to rebind to the entire collection (which would update the View).
If your Model implements INotifyPropertyChanged, the other option would be to have your ViewModelB class listen for changes on it's wrapped Model, and raise property changed events as needed.
Ideally, you'd be able to do like Reed Copsey indicated... Implement INotifyPropertyChanged on the model and have ViewModelB listen for those events. Then ViewModelB will pick up the changes no matter where the update happens.
However, In some cases the model doesn't (or can't) implement INotifyPropertyChanged. In this case, you may want to consider using an Event Aggregator pattern to pass a message between ViewModelA and the ViewModelB instances.
In this case, you could publish a "model changed" message from ViewModelA. The ViewModelB instances would subscribe to this message and each of them would get notified when A published the message. Then ViewModelB could raise the approriate PropertyChanged events to tell the UI what's changed.
You can find more info on Event Aggregator in many of the frameworks, including
Prism
Caliburn
MVVM Light
To solve this I created a class called VeryObservableCollection. For each object you add, it hooks the object's NotifyPropertyChanged event to a handler that triggers a CollectionChanged event. For each object removed, it removes the handler. Very simple and will give you exactly what you want. Partial code:
public class VeryObservableCollection<T> : ObservableCollection<T>
/// <summary>
/// Override for setting item
/// </summary>
/// <param name="index">Index</param>
/// <param name="item">Item</param>
protected override void SetItem(int index, T item)
{
try
{
INotifyPropertyChanged propOld = Items[index] as INotifyPropertyChanged;
if (propOld != null)
propOld.PropertyChanged -= new PropertyChangedEventHandler(Affecting_PropertyChanged);
}
catch (Exception ex)
{
Exception ex2 = ex.InnerException;
}
INotifyPropertyChanged propNew = item as INotifyPropertyChanged;
if (propNew != null)
propNew.PropertyChanged += new PropertyChangedEventHandler(Affecting_PropertyChanged);
base.SetItem(index, item);
}
If change in Models will change in some properties of ViewModelB and those properties has change notification to UI (i.e. ViewModleB is implementing INotifyPropertyChanged), the change will immediately reflect in the UI.
So, if you have a ObservableCollection of another viewmodelB, you need not to hook up events for the propertychanged of that viewmodelB. According to my understanding, whoever changes viewmodelB's properties (model class or anyone else) and if the properties has change notification, the view will update automatically.
I am working with a WPF application and using my own architecture that strongly resembles a M-V-VM /MVC. I have controllers for each of the views, and I have ViewModels that are bound to the Views.
For Example, I have a ToolBarView that has a corresponding ToolBarViewModel, and ToolBar Controller.
I am using notifications to update all the views so that they do not need to reference each other, but that is not relevant for my question.
Each of the Views is listening for an event to trigger in their controller when the model has been updated. In the ToolBarView this looks like the following.
/*In Constructor in ToolbarView*/
Controller.Updated += UpdateView
/*Event Handler in ToolbarView*/
private void Updateview(object sender,EventArgs e)
{
DataContext = Controller.Model;
//Other Updating if needed
}
If not obvious, what the above code is doing is saying that when the Controller fires the Updated event, to invoke the UpdateView(object sender,EventArgs e).
The problem that I am experiencing is that the first time that the UpdateView() is invoked, everything is working fine, but when it is invoked the second time, the DataContext seems to still be bound to the original Controller.Model.
It seems almost as if I have to release the DataContext, or refresh it in order for it to be bound to the Model every time.
The Controller is performing operations on the Model, and therefor when the UpdateView() is invoked, it needs to display the newly assigned values on that model.
Is there some way I need to refresh the DataContext, or is there a different way I need to do this?
If you are assigning the DataContext to the same instance of your model, the in effect it won't "change". WPF expects objects to notify it when their state changes, either through DependencyProperty properties or by implementing INotifyPropertyChanged.
So if you do something like:
MyObject o = new MyObject();
o.MyString = "One";
this.DataContext = o;
// ... some time later ...
o.MyString = "Two";
this.DataContext = o;
Assuming MyObject doesn't implement INotifyPropertyChanged, then the second assignment to DataContext is effectively worthless. You would have to set DataContext to null, then assign your object again to have it "refresh".
But your best bet in general would be to implement INotifyPropertyChanged. This would end up being much more efficient, as only the property that actually change would need to be updated in the UI.
I'm getting confused; I thought I understood INotifyPropertyChanged.
I have a small WPF app with a frontend MainWindow class, a viewmodel in the middle and a model at the back.
The model in my case is the Simulator class.
The SimulatorViewModel is nearly transparent and just interfaces properties between MainWindow and Simulator.
Simulator implements INotifyPropertyChanged and each property setter in Simulator calls the RaisePropertyChanged method:
private string serialNumber;
public string SerialNumber
{
get { return serialNumber; }
set
{
serialNumber = value;
RaisePropertyChanged("SerialNumber");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
In the xaml, I have a TextBox with binding like this:
Text="{Binding Path=SerialNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
and the DataContext is the SimulatorViewModel (but see my comment about changing the DataContext to the model)
The ViewModel just passes the properties through:
public string SerialNumber
{
get { return Simulator.SerialNumber; }
set { Simulator.SerialNumber = value; }
}
Programmatic updates to the property SerialNumber in Simulator are not propagating up to the UI although curiously the initial value, set in the Simulator constructor, is getting there.
If I break on the SerialNumber setter in the Simulator and follow into RaisePropertyChanged, I find that PropertyChanged is null and so the event is not propagating upwards to the GUI.
Which leads me to several questions:
What exactly should be hooking into the PropertyChanged event? I'm looking to be more specific than just "WPF". What's the link between that event and the Binding statement in the xaml?
Why is the initial property value getting up to the UI at startup, but not subsequently?
Am I right to have Simulator (the model) implementing INotifyPropertyChanged, or should it be the ViewModel doing that? If the ViewModel does it, then programmatic changes in the model don't trigger PropertyChanged, so I'm unclear on the correct pattern. I realise my ViewModel is virtually redundant, but that's due to the simplicity of the project; a more complex one would make the ViewModel concept work harder. My understanding is that the ViewModel is the place to interface my unit tests.
The problem is that you're raising PropertyChanged on your model, but your view is bound to ViewModel. So your View subscribes only to ViewModel events (not Model's) - that is why textbox is not updated - because it never receives PropertyChanged event. One of possible solutions is to listen in ViewModel for Model PropertyChanged events and raise same event on ViewModel accordingly.
Initial value is being propagated because your setters/getters in ViewModel are correct, the problem is in events.
Yes, you are correct (see my #1), you should raise PropertyChanged on your ViewModel because View is bound to it. These events will not be triggered automatically after changes in Model, so you should listen for Model.PropertyChanged in ViewModel.
Simplest dirty fix to understand the idea:
SimulatorViewModel(Simulator model)
{
// this will re-raise Model's events on ViewModel
// VM should implement INotifyPropertyChanged
// method OnPropertyChanged should raise INPC for a given property
model.PropertyChanged += (sender, args) => this.OnPropertyChanged(args.PropertyName);
}
Always make sure that the instance of the class which has been set as Data-context should implement INotifyPropertyChanged
In your case Simulator implements INotifyPropertyChanged but SimulatorViewModel is set as DataContext, in this case the UI will come to know only if there are changes in the SimulatorViewModel but not in the Simulator.
What you can do is that:
You can expose some events or delegates in the Simulator class and hook to some methods in SimulatorViewModel class. Then when ever values in Simulator class changes, invoke the event or the delegate that will get Execute on SimulatorViewModel, now you can get the updated values from the simulator and update the properties in SimulatorViewModel. Make sure that you use different properties in both the class