I am using WPF Prism 6 with autofac and having issues navigating between views. What I have is a view that I only want to keep alive till I leave it, and the next time I navigate to it, I want to create a new version of this view.
On load, I regist an IModule that has the following code
_regionManager.RegisterViewWithRegion(RegionNames.MainRegion,
typeof(DxfDisplay.Views.DxfDisplay));
This registers my view and the system works on initial load, I implement the INavigationAware and IRegionMemberLifetime interfaces on the view model and have public bool KeepAlive => false; implementing the IRegionMemberLifetime so that my view is disposed when I am done.
When I navigate away from this view everything is fine, but when I attempt to navigate to navigate to the view using
_regionManager.RequestNavigate(RegionNames.MainRegion,
new Uri("DxfDisplay", UriKind.Relative), parameters);
The view is not opened and a view model constructor is not called. To make the navigation work correctly, I need to register with view with the region again. Or if I change the KeepAlive to true I can navigate back to the original view, but I cannot generate a new view if INavigationAware.IsNavigationTarget returns false.
My question is how do I register the view with the region manager in such a way that when I call _regionManager.RequestNavigate, it will create a new instance of the view and display it. I feel like I am missing something simple and just overlooking it.
_builder.RegisterTypeForNavigation<DxfDisplay.Views.DxfDisplay>();
In Prism 7, this is now called RegisterForNavigation<T>() and exists on the IContainerRegistry interface.
For example, in your module:
public class MyModule : IModule
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<MyView>();
}
}
Related
Bellow you can see my bootstrapper. I want to register all the views from the bootstrapper.
When I start the application, WebView and EditView are created. GeneralView is a part of EditView and I have to navigate first to EditView in order to instantiate it.
How can I instantiate all the views when starting the executable?
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
// Register views
IRegionManager regionManager = this.Container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(WebView));
regionManager.RegisterViewWithRegion("ContentRegion", typeof(EditView));
// The following view is instantiated for the first time when I navigate to EditView
regionManager.RegisterViewWithRegion("GeneralRegion", typeof(GeneralView));
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void InitializeModules()
{
base.InitializeModules();
}
}
A view shouldn't be instantiated before it is actually being displayed on the screen. Besides, a view should only define the user interface.
If you expect a specific view model to be alive when you are sending an event using the event aggregator from another view model, you are actually introducing an indirect coupling between these two view models. And this is exactly what you want to avoid by using an event aggregator in the first place.
So if you rely on all events being handled, you should probably consider using a shared service that you instantiate as a singleton in the bootstrapper. You could then inject your view models with this shared service and communicate between them through the service interface.
I using MvvmCross to build my UWP application. I have views with their own view models.
When I start the app then first time from Setting view navigate to Passcode view using ShowViewModel<PasscodeViewModel>(). Then it call view model and view constructor to build and initialize view. When user come back to setting view and again navigate to passcode view using same method like ShowViewModel<PasscodeViewModel>() then this time view and view model constructor not get called. Since unable to reinitialize passcode view. So it display previous instance of passcode view.
Following solution I have tried
I tried removing the backstack in the navigations but then also not constructor get call.
Also implement loaded event but this solution also not working for me.
Also implemented Void Init() in view model but this method also not get call.
I just want when I navigate to PasscodeView then each time it call constructor of PasscodeView and PasscodeViewModel.
So my question is how to force to re-initialize page and viewmodel each time while navigation??
Please help to resolve this issue.
I needed to use navigation cache mode on required.
I resolved it for me with the not very clean solution to call init again.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (this.ViewModel!= null && e.NavigationMode != NavigationMode.Back)
{
var reqData = (string)e.Parameter;
var converter = Mvx.Resolve<IMvxNavigationSerializer>();
var req = converter.Serializer.DeserializeObject<MvxViewModelRequest>(reqData);
this.Vm.CallBundleMethods("Init", new MvxBundle(req.ParameterValues));
}
base.OnNavigatedTo(e);
}
This code avoids to call init again on back navigation.
I just set Universal Windows Phone apps page navigation cache mode "Disabled". I think its default value is "Required".
public PasscodeView()
{
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Disabled;
}
The above code work for me.
This is more complex than it sounds. I'm implementing MVVM pattern, which states a ViewModel cannot have a reference to it's View. That being said, I'm implementing Page navigation so that changes in views are done by using NavigationService in the View's code behind (let's say by pressing the "Next" button).
At some point in the program, we need to change Page using a voice command instead of a button (using speech recognition), and that logic is handled in the ViewModel (which doesn't have a reference to NavigationService).
So, without keeping a reference to thew View inside the ViewModel, how can I change page using NavigationService?
You could publish a "next page requested" message from within your view model using something like an event aggregator. Your view then subscribes to the message and handles it by using NavigationService to change the page. If you are using an MVVM framework most of them provide a way to publish / subscribe to messages out of the box.
In your Core (nonUI) project that containts your view models. Create a INavigationService interface:
public interface INavigation
{
void Navigate(IViewModel viewmodel);
void GoBack();
}
Then in your UI project, provide the implementation for that interface. You can get fancy with how you provide that implementation to the view model.
In the simple form you'd want to do something like:
public class MyViewModel
{
public MyViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
}
When the app starts up give the view model the implementation. At that point all your navigation can live in the view model. If you need to navigate from a view, execute a command on the view model and have it navigate.
Take a look at how MvvmLight does it:
INavigationService,
NavigationService
Let's say I have a ViewModel:
public class MyViewModel : MvxViewModel
{
public override void Start()
{
base.Start();
//Important things would happen here!
}
}
When I would use now ShowViewModel<MyViewModel>(); from any other ViewModel, it will walk into the overrided Start() Method from the MyViewModel.
However it doesn't walks into when I'm navigating from a Back/Return-Button.
The BackButton was built like that:
class MyPresenter : MvxModalSupportTouchViewPresenter
{
public INMobileAdminPresenter(UIApplicationDelegate applicationDelegate, UIWindow window)
: base(applicationDelegate, window)
{
}
protected override UINavigationController CreateNavigationController (UIViewController viewController)
{
var toReturn = base.CreateNavigationController (viewController);
toReturn.NavigationBarHidden = false;
return toReturn;
}
}
In the AppDelegate.cs I'm doing then following:
[Register("AppDelegate")]
public partial class AppDelegate : MvxApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
//var presenter = new MvxTouchViewPresenter(this, window);
var presenter = new MyPresenter(this, window); //Here my Presenter instead of the standard one
//and so far....
}
}
It's important for me to walk into the Start() Method from the ViewModel everytime - regardless if I'm using now ShowViewModel or if the navigation goes from the BackButton because I'm Subscribing there some relevant Eventaggregationmessages.
Any help appreciated!
Why is there a difference between navigating to the View (Back Button and ShowViewModel)?
The Back button navigation is part of iOS - built into the UINavigationController
ShowViewModel navigation is something built into MvvmCross.
The Start call is part of the MvvmCross ViewModel construction mechanism - see Wiki:How ViewModels are constructed - so it is only called when a new ViewModel is constructed - it's not called each time a ViewModel's View is made visible - which could be via back, via tab switching, etc.
How to solve it?
If you really want to change the Back navigation so that it calls Start - then you will need to change Back so that it constructs a new ViewModel. You could probably do this using some form of special UINavigationController (perhaps via a delegate) and/or intercepting the left bar button items so that they perform ShowViewModel calls. Depending on your UI, you may also need to intercept other calls within the UI too (e.g. if you are using tabs, flyouts, fragments or some other presentation method).
Alternatively, if you just want to intercept View events like OnNavigatedTo, ViewWillAppear, and OnResume and to pass those on to your ViewModel, then you can do this by not using Start, but instead adding some custom IActiveViewModel interface to your ViewModel and then calling this from appropriate hooks within your views. For more on this approach there are some comments and notes in:
https://github.com/slodge/MvvmCross/wiki/View-Model-Lifecycle#viewmodel-deactivation-activation-and-destruction
MvvmCross ViewModel caching and re-initializing
How do I update the parent viewmodel when child viewmodel is updated
Reinitialize ViewModel after coming back from another page
In a MVVM WPF application.
How do you set a second windows parent from the ViewModel?
example:
view1 -- viewModel1
viewModel1's command calls:
var view2 = new view2
view2.Owner = <----This is the problem area. How do I get view1 as the owner here from the viewModel?
view2.Show()
EDIT:
See accepted answer below, then read the following edit.
I'am using MVVM light -> http://mvvmlight.codeplex.com/ (awesome btw)
The baked-in messaging system is great. I am now sending a message from the viewmodel to my view telling it to show another window.
For the message I'am currently using a string with a switch statement in the main view to determine what view to open; however I may tinker with the tokens that also are part of MVVM light toolkit.
Thank you!
In my opinion, opening a new window is the responsibility of the View, not of the ViewModel. Personally, I would use the same approach as used for displaying a dialog box (this was discussed in this forum already):
Have the ViewModel send a Message to the View requesting that it opens a new Window.
(alternatively) use an IDialogService or whatever you want to call it which you pass to the ViewModel's constructor. This service will be in charge of opening the Window (or of delegating this task to the View).
This way, you keep a clean separation of concerns and your VM remains testable (you can unit test that the request to open the new WIndow has been sent, but you couldn't test that the window has been, indeed, open).
Does that make sense?
Cheers,
Laurent
From your viewmodel call
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("Open Window"));
And from your view's codebehind (a view that call the second
view) easily write this in the constructor:
Messenger.Default.Register<NotificationMessage>(this, ReplyToMessage);
And also write this method in the view's codebehind:
private void ReplyToMessage(NotificationMessage msg)
{
if (msg.Notification == "Open Window")
{
SecondWindow win = new SecondWindow();
win.ShowDialog();
}
}
I don't have an answer of my own but here's a few links to things I've been looking at lately that might help. I'll also be interested in anything others suggest.
As I understand it, the key thing is, you shouldn't be creating Views from within a View Model if possible, so you need a means of communicating what you need in a loosely coupled fashion.
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
http://www.codeproject.com/KB/architecture/MVVM_Dialogs.aspx
Handling Dialogs in WPF with MVVM
You can do in this way like you need to create some events and register those in view and call these in view model.and open that pop up window.
Like This example
public class Mainclass : MainView
{
public delegate abc RegisterPopUp(abc A);
public RegisterPopUp POpUpEvent;
public RelayCommand ShowCommand { private set; get; }
public void ShowCommand()
{
ShowCommand("Your parameter");
}
}
inside the view
MainView mn = new MainView();
Register the event here like mn.POpUpEvent += then click on tab button double time and in registers popup method write the code for opening the pop up window.
Prism-Event Aggrigator is good approach, where we can create independent module without dependency. first viewmodel will publish event and then another view or view or viewmodel can subscribe that event from event aggrigator.
in this case Unity container can also use to inject one viewmodel in to another with dependency injection.