ViewModels are not disposed in MvvmCross - how to manage subscriptions? - c#

Let's consider a simple application that consists of two views and two view models.
There is a button on FirstView which fires ShowViewModel and a button on SecondView which fires ShowViewModel.
Let's say that FirstViewModel also subscribes to messages of type MyMessage in the ctor.
Scenario: the user starts application, presses button on FirstView and than on SecondView. Now we have the following back stack: FirstView->SecondView->FirstView. Since ShowViewModel always creates a new instance we actually have TWO FirstViewModel objects in memory. Each of them is subscribed to MyMessages!
What can I do to avoid this? I want to make sure that only one instance of each ViewModel class is subscribed at the same time. Do I have to use platform dependent handlers in Views (like OnNavigatedTo/OnNavigatedFrom) to avoid this? Or is there a cross platform way?

Related

Passing Parameters to Shell Viewmodel Using Unity BootStrapper With Prsim

I've created a dialog service using interfaces to show custom dialog/confirmation boxes (I know that Prism comes with this ability but the dialog boxes don't appear to be customizable and don't match my layout/color scheme). The dialog service has a register function that registers the dialog view type with dialog viewmodel type in a dictionary. This is done so that a viewmodel can be passed in and in a loosely coupled fashion, an instance of the needed view can be created. The code looks like this:
private readonly IDialogService dialogService = new DialogService(Application.Current.MainWindow);
// Registers the confirmation window viewmodel with the confirmation window view
dialogService.Register<ConfirmationWindowViewModel, ConfirmationWindow>();
so my initial thought was to try to create this in the unity bootstrapper (because of the registration passing in views and viewmodels) but I can't seem to find a way to do that and pass in the dialog service.
I must note that the constructor for the main window viewmodel also injects the region manager and the event aggregator for Prism; I had tried creating an instance in the bootstrapper and registering the instance but the creation of the region manager vs the injection causes errors. If I declare and initialize the dialog service in the main window viewmodel it of course works but from my understanding of MVVM we don't want the viewmodels to have any knowledge of the views so I'm trying to find another way to do it, without breaking IoC for region manager and event aggregator.
I am new to MVVM and Prism/Unity so my grasp of these concepts isn't fully solidified yet.
I know that Prism comes with this ability but the dialog boxes don't appear to be customizable and don't match my layout/color scheme
You can create whatever you like as dialog, just derive from PopupWindowAction and override CreateWindow (and other methods as needed) to create the dialog you always wanted.
In case anyone sees this later and is curious, my end decision was to get rid of the 'Register' function altogether in favor of a solid convention instead.
Previously, I would use this function and kept a dictionary of all the registered views/viewmodels:
dialogService.Register<ConfirmationWindowViewModel, ConfirmationWindow>();
this would register take the and store them in the dictionary so I could later pass in a viewmodel and create an instance of the appropriate confirmation message view. Instead I removed all code regarding this part of the solution and replaced it with some reflection mixed in with naming conventions.
Step 1: Ensure all views are named with the suffix View at the end.
Step 2: Ensure all viewmodels are named with the suffix ViewModel at the end.
Step 3: Ensure these are all in appropriately named namespaces (views in views namespace and viewmodels in viewmodels namespace).
(most of this ^^ is done anyway)
Final Step: Replaced dictionary with this code:
var viewTypeName = viewModel.GetType().AssemblyQualifiedName.Replace("Model", "");
var viewType = Type.GetType(viewTypeName);
in the dialog interface. Now, no matter what viewmodel is passed in, it will pull the appropriate view with less code and no necessary linking as was done before.

Calling child's viewmodel method from main Viewmodel

I see in many posts that communications between viewmodels should be done using a messenger bus.
What I wonder is, is it considered bad to simply execute a child vm method from the main vm?
Suppose having a view with a TabControl, where each TabItem is a child viewmodel.
When I close my application, I need to store my settings calling a SaveSettings() child vm method.
I need to know if it's a bad code, and in case why.
main view
-> closingEvent
-> call MainVM SaveAllSettings()
-> for (ChildVM childVM in allTabs)
--> childVM.SaveSettings() (public method)
If your objects are already coupled to each other, i.e. if a parent view model already has a strong reference a child view model, there is no need to use a messenger or an event aggregator to communicate between them. Then you can (or rather should) call any methods of the child directly.
There are certainly cases where these kind of parent/child relationsships exist and it's not necessarily a bad thing or a sign of poor design. It all depends on the relationsships of your objects really.

Xamarin Forms: How notify my previous viewmodel than is observable collection need to be modified?

I use an observable collection with a selecteditem (name + detail) to push a new contentpage in my navigation and in this new page i modify the name of this selected item but in an other list.
I would like to refresh the data in the observable collection with this other list (saved in an internal storage)
So, can i use an event to notify the previous viewmodel than i push the back button and if it possible which event can i use?
Xamarin.Forms.Page
//
// Summary:
// When overridden, allows application developers to customize behavior immediately
// prior to the Xamarin.Forms.Page becoming visible.
//
// Remarks:
// To be added.
protected virtual void OnAppearing();
This may be helpfull you'll need to override it in your page code.
Overriding the OnAppearing method seems the simplest way to achieve this. This way you'll keep the logic to refresh the data in the page that belongs with the viewmodel that needs to be refreshed. When the page re-appears, it can trigger some logic in the ViewModel to refresh the ObservableCollection.
Another option is to use the MessagingCenter that comes with Xamarin.Forms: https://developer.xamarin.com/guides/xamarin-forms/messaging-center/ This allows pub/sub style messaging between components while staying loosely coupled. You could have the class that manages the internal storage publish messages whenever the list in the gets updated and broadcast this to ViewModels that have subscribed to these updates.
In this particular case, overriding OnAppearing seems the simplest solution though.

ViewModel calling a method of a user control (web browser)

I am trying to program in MVVM and I have the following use case:
A TextBox's text is bound to a property in the VM
A Button is command bound to a relay command
When the user presses the Button, the web browser's Navigate(url) method is called with the URL being the text in the TextBox
Above is the use case I want to create, but 1 and 2 is possible using the MVVM design pattern, but I could not find an adequate way to invoke the browser's Navigate() method. First of all, is it possible to call a method of a control from VM (please let me know if there is a way)? And in the above use case, what would be the appropriate way to structure the program if it is not possible?
Thanks
You could do the following:
Add a property MyUrl to your ViewModel
Bind MyUrl to your WebBrower's Source property
Make sure the property implements INotifyPropertyChanged. Then your Xaml:
<WebBrowser Source="{Binding MyUrl}" />
What if you REALLY wanted to call a UI method from the ViewModel?
If you ever do run into a situation where you absolutely need to call a method on a UI control for instance, you can hook up events on the ViewModel and then your UI registers to this event and does something UI specific...
VM code...
//... some VM logic
EpicNavigateEvent(url) // raise event, allowing UI to handle how
In your code-behind on your view (this is the part where some MVVM purests freak), you could register the event:
myVm.Navigate += doSomeNavigation;
...
public void doSomeNavigation(string url)
{
// call Navigate
}
I've successfully used this approach for applications where we have a single ViewModel layer and multiple technologies hooked up the views (WinForms, WPF and Asp.Net).
If you're looking for something more elegant, have a look at the User Interaction Patterns on MSDN.
The concept is the same though: Call something on the VM and the View is handles it appropriately.
Common scenarios for this type of approach is want to show a message to the user from the VM. Your VM should raise an event saying: ShowMyMessage("You're awesome"), then your UI is notified and handles it: MessageBox.Show(msg) or whatever.
As long as you stick to there rules you should be golden:
ViewModels should NOT be concerned about UI code
Views must ONLY handle the presentation of the data provided by your ViewModels.
Don't overcomplicate it. KISS...

Caliburn.Micro screen transition via conductor

I have a Caliburn.Micro shell (i.e., an empty XAML view to contain other views) rendered by a Conductor ViewModel. From there I open a Screen via:
ActivateItem(...)
Usually from the newly displayed dialog the user can perform some operations and click buttons (OK, Cancel, Build....) which should each transition to another screen (in the shell).
public MyDialog : Screen
{
public void Ok()
{
// TODO: Somehow tell the conductor or called of this class about this action.
}
}
What are good ways to achieve these kind of dialog action/message screen transitions?
Simple .NET events are possible -- Wouldn't that be a bad idea?
CM IEventAggregator should also work by changing the view
Checking from the shell Conductor the ViewModel result once it has been closed via TryClose() -- Should be possible, just don't know how to achieve this in CM.
Reference the shell Conductor instance from that screen (via IoC or directly) -- That seems strong coupling.
Could you please advise.
My preferred approach is to use the EventAggregator to facilitate messaging between VMs.
This works especially well when you have multiple windows which are listening for a certain type of event (e.g. a Visual Studio style interface with multiple tool windows which may show context sensitive properties), however it sounds a little overkill for this implementation. Of course the advantages are still a good loose coupling between VMs and a lack of events (which is a good thing!)
It sounds like you want a modal dialog to popup and present an option, and then activate another screen once the first one has returned.
You can attach an event handler to the Deactivated event in the child VM which will fire when an item deactivates. It also passes a boolean in the arguments to notify if the item which deactivated was closed - you can check for this and activate the corresponding screen in your conductor.
e.g.
this.Deactivated += new EventHandler<DeactivationEventArgs>(WorkspaceViewModel_Deactivated);
void WorkspaceViewModel_Deactivated(object sender, DeactivationEventArgs e)
{
if(e.WasClosed) // raise some event
}
Then pass an event up to the conductor, I wouldn't really go the event route for this. This couples the VMs one-way so it may not be the most flexible solution
The alternative is to fire a message via the event aggregator to tell the conductor it needs to open a different window when the child VM closes. The same method can be used but it's decoupled
this.Deactivated += new EventHandler<DeactivationEventArgs>(WorkspaceViewModel_Deactivated);
void WorkspaceViewModel_Deactivated(object sender, DeactivationEventArgs e)
{
if(e.WasClosed) MainConductor.EventAggregator.Publish(new ActivateWindowMessage(typeof(SomeVM));
}

Categories