Navigation between lazy loaded modules with Prism and WPF - wpf

Hello I'm trying to setup an architecture where only one module gets booted when the app is launched. Then I'd like to lazy load other modules based on the user's actions.
To achieve this in my app.xaml.cs I have one module loaded at bootstrap time (MainModule), and an other has InitializationMode = InitializationMode.OnDemand
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
Type BlipModuleType = typeof(BlipModule);
moduleCatalog.AddModule(new ModuleInfo()
{
ModuleName = BlipModuleType.Name,
ModuleType = BlipModuleType.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand
});
moduleCatalog.AddModule<MainModule>();
}
then my main module, which displays the view correctly, has a single view registered to the only region available:
public class MainModule : IModule
{
private readonly IRegionManager _regionManager;
public MainModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
The lazy loaded module has the same structure, registering a different view (which works properly if i decide to use it as my main module)
public class BlipModule : IModule
{
private readonly IRegionManager _regionManager;
public BlipModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewB));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
finally I have a Command in the viewmodel of my MainModule ViewA, that is supposed to load the new module and navigate to it.
public class ViewAViewModel : BindableBase
{
const string BlipModuleName = "BlipModule";
public ReactiveCommand ChangeRoute { get; set; } = new ReactiveCommand();
public ViewAViewModel(IRegionManager regionManager, IModuleManager moduleManager)
{
ChangeRoute.Subscribe(res =>
{
moduleManager.LoadModule(BlipModuleName);
});
moduleManager.LoadModuleCompleted += (s, e) =>
{
if (e.ModuleInfo.ModuleName == BlipModuleName)
{
regionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(BlipModuleName, UriKind.Relative));
}
};
}
}
The viewB of the BlipModule is actually loaded (I get a hit if I set a breakpoint in the view's constructor), but instead of the view I get a white page with "System.Object" inside of it.
Any idea? thanks!

You want to RegisterForNavigation instead of RegisterViewWithRegion.

Related

Proper way to handle prism navigation exception

I am using Prism Wpf 8.1.97, and DryIoc container.
In my scenario i have to navigate between two views, so I implemented the INavigationAware Interface provided by Prism Framework in my viewModels.
I call the IRegionManager RequestNavigate method to perform the navigation from the VMA to VMB.
Unfortunately, I forgot to register a IService dependency that is needed in the VMB Ctor, so when I perform the navigation process I get the exception in the navigation callback.
The IService interface
public interface IService
{
}
ViewModel A:
public class ViewAViewModel : BindableBase, INavigationAware
{
private readonly IRegionManager regionManager;
public ViewAViewModel(IRegionManager regionManager)
{
this.regionManager = regionManager;
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
regionManager.TryRequestNavigate("ContentRegion", "ViewB", new NavigationParameters());
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
ViewModelB:
public class ViewBViewModel : BindableBase, INavigationAware
{
public ViewBViewModel(IService service)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
}
I created an extension method to keep it simple..
public static class IRegionManager_Extensions
{
public static void TryRequestNavigate(this IRegionManager regionManager, string regionName, string target, NavigationParameters navigationParameters)
{
regionManager.RequestNavigate(regionName, target, NavigationResult =>
{
if (NavigationResult.Result == false)
{
MessageBox.Show(NavigationResult.Error.InnerException.Message, "Navigation Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}, navigationParameters);
}
}
Unfortunately what I get from the InnerException.Message is
ContainerException: code: Error.UnableToResolveFromRegisteredServices;
message: Unable to resolve Resolution root
Module1.ViewModels.ViewBViewModel from container without scope with
Rules with {TrackingDisposableTransients,
UseDynamicRegistrationsAsFallbackOnly, FuncAndLazyWithoutRegistration,
SelectLastRegisteredFactory} and without
{ThrowOnRegisteringDisposableTransient,
UseFastExpressionCompilerIfPlatformSupported} with
FactorySelector=SelectLastRegisteredFactory with
Made={FactoryMethod=ConstructorWithResolvableArguments} with normal
and dynamic registrations: (DefaultDynamicKey(0), {FactoryID=178,
ImplType=Module1.ViewModels.ViewBViewModel, Reuse=TransientReuse,
HasCondition})
without no reference to "IService"
I need to show that the navigation failed because IService implementation is not found in the container.
Is there a way to get the missing service Interface name from the exception?
Service name in the exception is null
Thanks.

Caliburn Micro content control navigation

I'm using caliburn micro for this project.
I have my ShellView with my contentcontrol:
<ContentControl x:Name="ActiveItem"
Grid.Row="0" Grid.Column="0" />
In ShellViewModel i got it to show my usercontrol LoginView with:
public class ShellViewModel : Conductor<object>
{
public ShellViewModel()
{
ActivateItem(new LoginViewModel());
}
public void ShowSignUp()
{
ActivateItem(new SignUpViewModel());
}
}
However, i can't navigate to SignUpView from LoginView with my button:
<!-- Row 4 -->
<Button x:Name="ShowSignUp"
Content="Sign Up Now!"
Grid.Row="3" Grid.Column="1"
Style="{StaticResource LoginBtnsStyle}" />
LoginViewModel deriving from ShellViewModel:
public class LoginViewModel : ShellViewModel
{
}
How do i navigate from LoginView to SignUpView with a button that is on the LoginView?
I'm getting no errors, it just isn't changing view.
I also tried putting ShowSignUp() on the LoginViewModel but no success.
Update 1 ShellViewModel:
public class ShellViewModel : Conductor<object>, IHandle<ActionInvokedMessage>
{
DispatcherTimer dt = new DispatcherTimer();
private SplashScreenViewModel _splashVM;
private LoginViewModel _loginVM;
private SignUpViewModel _signUpVM;
private IEventAggregator _eventAggregator;
public ShellViewModel(SplashScreenViewModel splashVM, LoginViewModel loginVM, SignUpViewModel signUpVM)
{
_loginVM = loginVM;
_signUpVM = signUpVM;
_splashVM = splashVM;
ActivateItem(_splashVM);
dt.Tick += new EventHandler(Dt_Tick);
dt.Interval = new TimeSpan(0, 0, 2);
dt.Start();
}
private void Dt_Tick(object sender, EventArgs e)
{
dt.Stop();
ActivateItem(_loginVM);
}
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
ActivateItem(new LoginViewModel(_eventAggregator));
}
public void Handle(ActionInvokedMessage message)
{
ActivateItem(message.Page);
}
public void ShowSignUp()
{
ActivateItem(new SignUpViewModel());
}
}
You could achieve this using EventAggregator to publish indicative messages from LoginViewModel to ShellViewModel to update the UI.
To begin with, you need to define an message class, which would tells the ShellViewModel which ViewModel needs to be changed. For example,
public class ActionInvokedMessage
{
public Screen Page { get; set; }
}
The Page property would indicate which Screen needs to be loaded. Now, you could change your LoginViewModel as the following.
public class LoginViewModel: Screen
{
private IEventAggregator _eventAggregator;
public LoginViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public void ShowSignUp()
{
_eventAggregator.PublishOnUIThread(new ActionInvokedMessage { Page = new SignupViewModel() }); ;
}
}
The PublishOnUIThread method would broadcast a message to all the listeners of the Message Type ActionInvokedMessage for the change. Next step would be to ensure the ShellViewModel would be listening to the change.
public class ShellViewModel : Conductor<object>, IHandle<ActionInvokedMessage>
{
private IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
ActivateItem(new LoginViewModel(_eventAggregator));
}
public void Handle(ActionInvokedMessage message)
{
ActivateItem(message.Page);
}
public void ShowSignUp()
{
ActivateItem(new SignupViewModel());
}
}
The Implementation of IHandle interface allows us to handle the action that would be required to be executed when the ShellViewModel recieves the ActionInvokedMessage. As seen in the code, this would be an appropriate place to use the ActivateItem method to load the Signup Page.
You can create an interface for navigation and use it in view models to navigate around app.
interface INavigation {
void NavigateTo(System.Type typeId);
}
class ShellViewModel: Conductor<object>, INavigation {
private List<object> pages = new List<Object>();
public ShellViewModel() {
pages.Add(new SignupViewModel(this));
pages.Add(new LoginViewModel(this));
}
void NavigateTo(System.Type typeId) {
var page = pages.Where(x => x.GetType() == typeId).FirstOrDefault()
ActivateItem(page);
}
}
class SignupViewModel {
public SignupViewModel(INavigation navigation) {
this.ShowLoginCommand= new NavigateCommand<LoginViewModel>(navigation);
}
}
class LoginViewModel {
public LoginViewModel (INavigation navigation) {
this.ShowSignUpCommand = new NavigateCommand<SignupViewModel>(navigation);
}
}
Navigation command may be implemented like follows:
public class NavigateCommand<T> : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly INavigation navigation;
public NavigateCommand(INavigation navigation)
{
this.navigation = navigation;
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => this.navigation.NavigateTo(typeof(T));
}
Here I pass System.Type but you can design type that better describes navigation request so that you may pass additinal paramters.

Open new window on click in WPF, Ninject and Caliburn.Micro

I'm trying to set up a WPF app to call the new window on a menu click with the data provider interface injected into the new viewmodel.
Followed many tutorials and created the Bootstrapper for Caliburn, a service locator and module for ninject. So far the main view doesn't need the IDataProvider but I'd like to open a new window on click event.
The Bootstrapper:
public class Bootstrapper : BootstrapperBase
{
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainScreenViewModel>();
}
}
The Service Locator and Module:
public class ServiceLocator
{
private readonly IKernel _kernel;
public ServiceLocator()
{
_kernel = new StandardKernel(new ServiceModule());
}
public MainScreenViewModel MainScreenViewModel => _kernel.Get<MainScreenViewModel>();
public NewLayoutViewModel NewLayoutViewModel => _kernel.Get<NewLayoutViewModel>();
}
public class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<ISqlite>().To<Sqlite>();
Bind<IDataProvider>().To<DataProvider>();
}
}
And this is where I got stuck:
public class MainScreenViewModel : Conductor<object>
{
private IWindowManager _windowManager;
public MainScreenViewModel()
{
_windowManager = new WindowManager();
}
public void NewLayout()
{
_windowManager.ShowWindow(new NewLayoutViewModel());
}
}
since the NewLayoutViewModel requires the IDataProvider.
Not sure, what am I missing, but in my understanding Ninject should take care of this di for NewLayoutViewModel.
Found a good solution from Tim Corey on YouTube.
Basically the answer is, if you not insist Ninjet, use Caliburn.Micro's build-in DI solution "SimpleContainer".

module dependencies in prism wpf

i am trying to code something similar to the project 'Modularity with unity'.
i built 2 modules: Module A (loaded via code) and Module D (loaded from directory).
both are loaded successfully.
but when i try to set dependency it's not working.
i can't really figure out where the dependency is mentioned in this project.
(i set the dependency as attribute in ModuleA class, moduleD is copied after build)
this is my implementation:
Bootstrapper.cs
protected override IModuleCatalog CreateModuleCatalog()
{
return new AggregateModuleCatalog();
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
Type moduleAType = typeof(ModuleAModule);
ModuleCatalog.AddModule(new ModuleInfo()
{
ModuleName = ModuleNames.ModuleA,
ModuleType = moduleAType.AssemblyQualifiedName
});
DirectoryModuleCatalog directoryCatalog = new DirectoryModuleCatalog() { ModulePath = #".\Modules" };
((AggregateModuleCatalog)ModuleCatalog).AddCatalog(directoryCatalog);
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
this.RegisterTypeIfMissing(typeof(IModuleTracker), typeof(ModuleTracker), true);
}
ModuleA.cs
[Module(ModuleName = ModuleNames.ModuleA)]
[ModuleDependency(ModuleNames.ModuleD)]
public class ModuleAModule : IModule
{
private ILoggerFacade _logger;
private IModuleTracker _moduleTracker;
public ModuleAModule(ILoggerFacade logger, IModuleTracker moduleTracker)
{
_logger = logger;
_moduleTracker = moduleTracker;
_moduleTracker.ModuleConstructed("ModuleA");
}
public void Initialize()
{
_logger.Log("ModuleA demonstrates logging during Initialize().", Category.Info, Priority.Medium);
_moduleTracker.ModuleInitialized("ModuleA");
}
}
ModuleD.cs
[Module(ModuleName = ModuleNames.ModuleD)]
public class ModuleDModule : IModule
{
private ILoggerFacade _logger;
private IModuleTracker _moduleTracker;
public ModuleDModule(ILoggerFacade logger, IModuleTracker moduleTracker)
{
_logger = logger;
_moduleTracker = moduleTracker;
_moduleTracker.ModuleConstructed("ModuleD");
}
public void Initialize()
{
_moduleTracker.ModuleInitialized("ModuleD");
}
}
perhaps that has something to do with the order in which your modules get loaded? As far as I see Module A gets loaded before ModuleD to which is has a dependency.
Don't know if that does help you but that was my first thought..
Which error message do you get?

MEF problem with import

models from shell-view-model with abstract factory pattern. I need inject in view-models classes from external assembly. If I use abstract factory pattern on creation view-models. Problem is imported classes in view-models are null.
Shell-view-models look like this:
public interface IViewModelFactory
{
ILogOnViewModel CreateLogOnViewModel(IShellViewModel shellViewModel);
IMessengerViewModel CreateMessengerViewModel(IShellViewModel shellViewModel);
}
[Export(typeof(IViewModelFactory))]
public class DefaulFactoryViewModel:IViewModelFactory
{
#region Implementation of IViewModelFactory
public ILogOnViewModel CreateLogOnViewModel(IShellViewModel shellViewModel)
{
return new LogOnViewModel(shellViewModel);
}
public IMessengerViewModel CreateMessengerViewModel(IShellViewModel shellViewModel)
{
return new MessengerViewModel(shellViewModel);
}
#endregion
}
public interface IShellViewModel
{
void ShowLogOnView();
void ShowMessengerView();
}
[Export(typeof(IShellViewModel))]
public class ShellViewModel : Conductor<IScreen>, IShellViewModel
{
private readonly IViewModelFactory _factory;
[ImportingConstructor]
public ShellViewModel(IViewModelFactory factory)
{
_factory = factory;
ShowLogOnView();
}
public void ShowLogOnView()
{
var model = _factory.CreateLogOnViewModel(this);
// var model = IoC.Get<LogOnViewModel>();
ActivateItem(model);
}
public void ShowMessengerView()
{
var model = _factory.CreateMessengerViewModel(this);
ActivateItem(model);
}
}
Some view-model.:
public class LogOnViewModel : Screen,ILogOnViewModel
{
[Import]//inject class from external assembly
private IPokecConnection _pokecConn;
private readonly IShellViewModel _shellViewModel=null;
private User _user=null;
public LogOnViewModel(IShellViewModel shellViewModel)
{
_shellViewModel = shellViewModel;
_user = new User();
}
}
variable _pokecConn are null becasuse I use abstract factory on creation new view-models.
if I use in shell-view model this:
var model = IoC.Get<LogOnViewModel>();
instead this:
var model = _factory.CreateLogOnViewModel(this);
and add Export attribute on view-models classes it works good, but I would like use abstract factory, and inject in view-model only classes from extrenal assembly.
It exist solution on this problem, or I must create view-models from IoC and export all class? Thanl for advance.
EDITED :
MEF BOOTSTRAPER CLASS:
public class MefBootStrapper : Bootstrapper<IShellViewModel>
{
#region Fields
private CompositionContainer _container;
#endregion
#region Overrides
protected override void Configure()
{ // configure container
#if SILVERLIGHT
_container = CompositionHost.Initialize(
new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
#else
var catalog =
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
//add external DLL
catalog.Catalogs.Add(
new AssemblyCatalog(string.Format(
CultureInfo.InvariantCulture, "{0}{1}", System.IO.Directory.GetCurrentDirectory(), #"\Pokec_Toolkit.dll")));
_container = new CompositionContainer(catalog);
#endif
var batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
_container.SatisfyImportsOnce(this);
}
protected override object GetInstance(Type serviceType, string key)
{
string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = _container.GetExportedValues<object>(contract);
if (exports.Count() > 0)
return exports.First();
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
}
protected override void BuildUp(object instance)
{
_container.SatisfyImportsOnce(instance);
}
#endregion
}
Did you forget the attribute ImportingConstructor for the LogOnViewModel constructor?
EDIT: Import property always null (MEF import issue)

Resources