How to unsibcribe events when I delete a ViewModel? - c#

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.

Related

Model View Presenter Events and Separation of Concerns

I am building a Windows Forms application that I would like to potentially port to WPF and GTK# in the future. I am interested in using the MVP pattern to accomplish this.
For a simple preferences dialog I have a designer created form that implements a view interface with events that the presenter can listen for when the dialog is saved or closed. I use the designer to create data bindings between the preferences frame's controls and the .NET project settings, so I am doing supervising presenter.
interface IPreferencesDialogView
{
event EventHandler Save;
event EventHandler Cancel;
}
public partial class PreferencesDialog : Form, IPreferencesDialogView
{
private PreferencesDialogPresenter presenter = null;
public event EventHandler Save;
public event EventHandler Cancel;
public PreferencesDialog()
{
InitializeComponent();
presenter = new PreferencesDialogPresenter(this);
}
private void PreferencesDialog_FormClosing(object sender, FormClosingEventArgs e)
{
if (this.DialogResult == DialogResult.OK)
{
Save?.Invoke(this, EventArgs.Empty);
}
else
{
Cancel?.Invoke(this, EventArgs.Empty);
}
}
}
My model uses the .NET project settings to store application settings since it is available in Mono and I can use it with both WPF and GTK#.
class PreferencesDialogPresenter
{
private readonly IPreferencesDialogView view;
public PreferencesDialogPresenter(IPreferencesDialogView view)
{
this.view = view;
view.Save += (o, e) => { Properties.Settings.Default.Save(); };
view.Cancel += (o, e) => { Properties.Settings.Default.Reload(); };
}
}
On my main form I also some very specific code to Windows Forms, a cascade button which cascades all open MDI windows. It's very simple using the LayoutMdi method provided by Windows Forms (something Java Swing does not have).
private void cascade_Click(object sender, EventArgs e)
{
this.LayoutMdi(MdiLayout.Cascade);
}
This to me seems to be working great so far. The view knows nothing about the model or the presenter and the model knows nothing about the view or the presenter. However, I have a few questions.
Is there anyway to simplify my event patterns? I really don't like having to pass arguments I do not use.
Ideally I would have only a single event, Closed, and I would forward the dialog result to the presenter. I do not like the Save/Cancel logic being in the view. However, the DialogResult type is Windows Forms specific, so I can't use it with GTK#. Could I create my own wrapper type? Is that what is usually done?
How would I go about showing this dialog? For example, on the Main Form I have a "Preferences" menu item. When it is clicked, who should the main form's presenter tell to open the preferences dialog, should it tell the preference dialog's view or presenter to show itself? This question arises because ShowDialog is obviously Windows Forms specific.
How would I even go about putting the MDI cascade logic into my presenter or is it not even worth it to bother in this case?
Is there anyway to simplify my event patterns? I really don't like having to pass arguments I do not use.
Ideally I would have only a single event, Closed, and I would forward the dialog result to the presenter. I do not like the Save/Cancel logic being in the view. However, the DialogResult type is Windows Forms specific, so I can't use it with GTK#. Could I create my own wrapper type? Is that what is usually done?
Yes, what I do and believe is the best practice is to create specific events that are related to the action occurring. NOT just passing events up from the UI. So, a single Close event including a simple enum to indicate whether to save or cancel. Your presenter would contain the logic to determine based on that enum whether to do Properties.Settings.Default.Save(); or Properties.Settings.Default.Reload();
Then in your non Windows Forms view, you would still need to invoke that event, but it would be up to the view to decide whether to save or cancel all the time, or whether to implement a custom Save/Cancel dialogue to get this info from the user.

Wpf event when DataContext is modified

You know how a TextBox will have its content modified when the DataContext changes (not replaced, just modified)? Well I want to be notified just like the UI is, with some sort of event on the DataContext. So far, I haven't found a way to do this, and I'm about to give up and simply subscribe to all the events on each INotifyPropertyChanged in my DataContext. I know there's the DataContextChanged / DataContextChanging in the Window class, but so far I either can't get it to work, or this only fires when the DataContext is replaced completely. Is there a way to do this?
Think about what is being asked. If any property changes on a data context a general event is to fire.
What process is available which can do that operation externally?
Decentralized Solution
Properties do not provide a change notification unless they are manually programmed to do so themselves; hence why INotifyPropertyChange is the route to normally use.
Centralized Solution
Otherwise a separate manager will need to reflect off of the instance taking a snapshot of all the properties. Then on a timer the manager will poll the instance for a current snapshot and compare that one to the old snapshot. If a change is detected an event can be fired and the new snapshot replaces the old.
DataContextChanged event is only fired when the DataContext of the Window has changed completely (set to null, or a new instance, etc.). I believe you are down the correct path, and inside your ViewModel, you will need to subscribe to NotifyPropertyChanged. In the event handler, you can switch on the corresponding property like so:
private void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.Property)
{
case "Property1":
break;
case "Property2":
break;
case "Property3":
break;
}
}

RejectChanges() with ViewModel and MVVM design pattern and UI update

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.

How can I notify that a property of an item in an observable collection has changed?

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.

Refresh DataContext for Views in WPF?

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.

Categories

Resources