I have a Prism Shell with two modules. One module is supposed to be the main application mock, MainAppMock, and the other module is supposed to be whatever that main system is using as a region, ModuleOne. Could be one, could be a million module.
The issue is understanding how Prism works. The MainAppModule initializes properly unless I call it's namespace in the Bootstrapper MainWindow.xaml file.
My question: Is this because it is loading the module at run time when I am calling that namespace and therefore Prism doesn't load it because it is already loaded? What is actually happening behind the scenes?
Shell:
class Bootstrapper : NinjectBootstrapper
{
protected override DependencyObject CreateShell()
{
return Kernel.Get<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow = (Window)Shell;
Application.Current.MainWindow.Show();
}
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog
{
ModulePath = AppDomain.CurrentDomain.BaseDirectory
};
}
}
MainAppMock and ModuleOne are the same except for the name.
ModuleOne Class:
[Module(ModuleName = "ModuleOne.Module")]
public class Module : IModule
{
private readonly IRegionManager _regionManager;
private readonly IKernel _kernel;
public Module(IRegionManager regionManager, IKernel kernel)
{
_regionManager = regionManager;
_kernel = kernel;
}
public void Initialize()
{
}
}
The problem is here. In the Bootstrapper MainWindow:
<Window x:Class="PrismTest.MainWindow"
xmlns:mainAppMock="clr-namespace:MainAppMock;assembly=MainAppMock"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<mainAppMock:MainUserControl />
</Grid>
Note: It works fine if I remove the mainAppMock namespace as mentioned above.
Prism has Modules and Shell. Let's imagine that Shell is TV-Set, consequently, Modules will be channels in TV-Set. At start of program (you turn-on TV-Set) in the method protected override IModuleCatalog CreateModuleCatalog(), you are loading Modules(TV-Set loads your searched channels):
protected override IModuleCatalog CreateModuleCatalog()
{
ModuleCatalog catalog = new ModuleCatalog();
catalog.AddModule(typeof(ModuleZooModule));//Channel about Zoo
catalog.AddModule(typeof(ModuleSportModule));//Channel about Sport
catalog.AddModule(typeof(ModuleProgrammingModule));//Channel about Programming
return catalog;
}
And usually Shell should look like this:
<DockPanel >
<ContentControl Margin="5"
prism:RegionManager.RegionName="{x:Static inf:RegionNames.ContentRegion}"
prism:RegionManager.RegionManager="{Binding RegionManager}"/>
</DockPanel>
The row prism:RegionManager.RegionName="{x:Static inf:RegionNames.ContentRegion}" shows to Prism where Module(video stream of channel) should be injected in your Shell(TV-Set).
But in your case is not eligible to use such things:
<Grid>
<mainAppMock:MainUserControl />
</Grid>
As Prism does not know where Module(some channel) can be shown in your Shell(TV-Set).
After discussing this with a few people we've reached one conclusion.
We are loading the MainAppMock by calling it's namespace:MainUserControl in the Shell MainWindow. When Prism's bootstrapper runs it will go in the AppDomain.CurrentDomain.BaseDirectory and search through the DLLs for anything that inherits from IModule. It realizes that MainAppMock does that, but it is already loaded. It does not make sense to load a DLL that has already been loaded. Therefore, it won't reload it and the Module will never run.
It is worth mentioning that the MainAppMock initialization method is running when the DLL is loaded. We can actually do our Prism Module code there, but that is not recommended.
Related
I have a Prism 7 / WPF / MVVM app that is configured with AutowireViewModel="True" in the views that have a viewmodel. Most of my view models have dependencies that I have configured in the Prism Unity container and these dependencies are injected into the viewmodel contructor. I do not explicitly create instances of views/viewmodels in the code behind anywhere. I also do not set the data context in XAML i.e. using DataContext element or d:DataContext). The only references in the XAML are to views (i.e. part of the HamburgerMenu control).
All is working fine except each view/viewmodel is ALWAYS constructed twice for some reason, whether this is the main window, or views within modules. I have placed breakpoints in the module managers (only gets hit once) - and also in the view constructors and viewmodel constructors which are both hit twice.
The following is some code for App class and also some code/view/viewmodel for a module named Messaging that is loaded at runtime not on demand. Due to wishing to use the MVVM pattern as much as possible, I have very little code in the code behind of the views. NOTE: I tried to see if this is just an issue with views for modules that are loaded on start up, but the same fate is present for the modules loaded on demand.
The App/PrismApplication class in full:
public partial class App : PrismApplication
{
private ILoggerFactory loggerFactory;
private TaskbarIcon _taskbarIcon;
protected override Window CreateShell()
{
// Register an instance of the Window (used by the dialog service to get a handle on the window when displaying dialogs)
Container.GetContainer().RegisterInstance(typeof(Window), "MainWindow", Container.Resolve<MainWindow>(), new ContainerControlledLifetimeManager());
return Container.Resolve<MainWindow>();
}
protected override void OnInitialized()
{
base.OnInitialized();
// Create the links to each module for the banner view to display
var menuService = Container.Resolve<IMenuService>();
var regionManager = Container.Resolve<IRegionManager>();
menuService.AddItem("Home", "Home", () => regionManager.RequestNavigate("MainRegion", "HomeMainView"));
menuService.AddItem("Messaging", "Messaging", () => regionManager.RequestNavigate("MainRegion", "MessagingMainView"));
menuService.AddItem("Charts", "Charts", () => regionManager.RequestNavigate("MainRegion", "ChartsMainView"));
menuService.AddItem("Admin", "Admin", () => regionManager.RequestNavigate("MainRegion", "AdminMainView"));
// Register banner view with region manager and display it
regionManager.RegisterViewWithRegion("BannerRegion", typeof(BannerView));
// Load the desired module into the main window on start up
Container.Resolve<IRegionManager>().RequestNavigate("MainRegion", "HomeMainView");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
var container = this.ConfigureLogging(containerRegistry);
// Register types
container.RegisterInstance<IDbConnectionFactory>(
new SqlConnectionFactory(
ConfigurationManager.ConnectionStrings["Messaging"].ConnectionString,
loggerFactory));
/************************************************************/
// TODO: Not sure if need singletons or not - to test......
/************************************************************/
containerRegistry.RegisterSingleton<IMetroMessageDisplayService, MetroMessageDisplayService>();
containerRegistry.RegisterSingleton<IUserService, UserService>();
containerRegistry.RegisterSingleton<IUserStore, UserStore>();
containerRegistry.RegisterSingleton<IMenuService, MenuService>();
containerRegistry.Register<ICustomerService, CustomerService>();
containerRegistry.Register<INotifyIconService, NotifyIconService>();
containerRegistry.RegisterDialog<DefaultDialog, DefaultDialogViewModel>("Default");
containerRegistry.RegisterDialog<HtmlDialog, HtmlDialogViewModel>("Html");
// Get the current user's details - prevent a deadlock in the way we use Task.Run(...).Result
// Then add to container as we will be injecting into VMs
var clientUser = Task.Run(GetCurrentUserDetails).Result;
containerRegistry.RegisterInstance(clientUser);
containerRegistry.RegisterSingleton<IClientUser, ClientUser>();
// Add the task bar icon
_taskbarIcon = (TaskbarIcon)FindResource("NotifyIcon");
containerRegistry.RegisterInstance(_taskbarIcon);
// Create a logger instance
var logger = loggerFactory.CreateLogger<App>();
logger.LogDebug("Finished registering types in App.xaml.cs");
}
private async Task<ClientUser> GetCurrentUserDetails()
{
var userService = Container.Resolve<IUserService>();
var data = await userService.GetClientUsersAsync(ConfigurationManager.AppSettings.Get("TempUserName")).ConfigureAwait(true);
if (!data.Any())
{
// log unable to load user from database then return as no point in loading messages for a user that cannot be found!!
return null;
}
return data.FirstOrDefault();
}
protected override IModuleCatalog CreateModuleCatalog()
{
// We are returning a type that reads the modules from the config file.
return new ConfigurationModuleCatalog();
}
protected override void OnExit(ExitEventArgs e)
{
// The icon would clean up automatically, but this is cleaner
_taskbarIcon.Dispose();
base.OnExit(e);
}
private IUnityContainer ConfigureLogging(IContainerRegistry containerRegistry)
{
// Configure logging - Needed Unity.Microsoft.Logging package
// see https://github.com/unitycontainer/microsoft-logging and https://github.com/unitycontainer/examples/tree/master/src/Logging/Microsoft.Logging
var serilogLogger = new LoggerConfiguration()
.ReadFrom.AppSettings()
.CreateLogger();
this.loggerFactory = new LoggerFactory().AddSerilog(serilogLogger);
var container = containerRegistry.GetContainer();
container.AddExtension(new LoggingExtension(loggerFactory));
return container;
}
}
The messaging module:
public class MessagingModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
// Register main view with region manager
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("MainRegion", typeof(MessagingMainView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IMessageStore, MessageStore>();
containerRegistry.RegisterSingleton<IMessageService, MessageService>();
containerRegistry.RegisterSingleton<IChatService, ChatService>();
containerRegistry.RegisterSingleton<IFileDialogService, FileDialogService>();
containerRegistry.RegisterDialog<InboxMessageView, InboxMessageViewModel>("HtmlMessage");
// Here we are loading the config/settings from the app.config for this module so we
// can register a ChatServiceConfiguration type as it is injected into ChatService
var filename = Assembly.GetExecutingAssembly().Location;
var configuration = ConfigurationManager.OpenExeConfiguration(filename);
if (configuration != null)
{
var hubUrl = configuration.AppSettings.Settings["HubUrl"].Value;
if (string.IsNullOrEmpty(hubUrl))
{
throw new ArgumentException("The HubUrl app setting cannot ne null or whitespace.");
}
containerRegistry.RegisterInstance(new ChatServiceConfiguration(hubUrl));
containerRegistry.RegisterSingleton<IChatServiceConfiguration, ChatServiceConfiguration>();
}
}
}
The MessagingMainView:
<UserControl x:Class="Ascensos.Wpf.Modules.Messaging.Views.MessagingMainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:materialDesignConverters="clr-namespace:MaterialDesignThemes.Wpf.Converters;assembly=MaterialDesignThemes.Wpf"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:Ascensos.Wpf.Modules.Messaging.Views"
xmlns:helpers="clr-namespace:Ascensos.Wpf.Modules.Messaging.Helpers"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
</UserControl>
The code behind:
public sealed partial class MessagingMainView : UserControl
{
public MessagingMainView()
{
this.InitializeComponent();
}
}
The viewmodel (calls a base message view model class):
public sealed class MessagingMainViewModel
{
public MessagingMainViewModel()
{
}
}
UPDATE: I have removed code from my app, so that only the above code is within the view and viewmodel. The view constructor and viewmodel constructor is still being hit twice during the module initialization.
I do not explicitly create instances of views/viewmodels in the code behind anywhere (i.e. only in XAML).
This is a contradiction - creating a view model in xaml means creating one in the same way as in code behind or anywhere else.
As said before, just don't do it. Remove all references to your view models' constructors from xaml, too (like <DataContext><MyViewViewModel/></DataContext>). To get intellisense in xaml, use d:DataContext.
I have found the issue. It is the following line in App CreateShell() method:
Container.GetContainer().RegisterInstance(typeof(Window), "MainWindow", Container.Resolve<MainWindow>(), new ContainerControlledLifetimeManager());
Commenting this out has sorted the problem. Looking at it again I have been rather silly to not spot this, like the novice I am. This code was added so I could get access to the MainWindow/MetroWindow in a service class - but I will need to try this another way. Thanks for your time #Haukinger.
I follows samples code provided in https://github.com/PrismLibrary/Prism-Samples-Wpf/blob/master/17-BasicRegionNavigation
I want to achieve the following result when I run the application (without explicitly clicking Navigate to View A). Does anyone know how to do it?
I have tried adding Navigate("ViewA"); after this line. However, I cannot get the desired outcome. Is it because the module hasn't been initialized?
Thanks.
did you add your module to the modulecatalog using override method ConfigureModuleCatalog? take a look at here
Eventually I solve by adding the following code in MainWindow.xaml.cs
public partial class MainWindow
{
IRegionManager _regionManager;
public MainWindow()
{
InitializeComponent();
_regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
RegionManager.SetRegionManager(ContentRegion, _regionManager);
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_regionManager.RequestNavigate("ContentRegion", "ViewA");
}
}
Get idea from: https://github.com/MahApps/MahApps.Metro/issues/1020#issuecomment-44779574
I'm kinda late to the party here, but I also stumbled over the question of how to navigate to a default view during the applications startup.
I found two ways:
1. App decides the default view
This can be solved in the CreateShell()-override in the App-Class.
This is my CreateShell-Method:
/// <inheritdoc />
protected override Window CreateShell()
{
var window = this.Container.Resolve<MainWindow>();
window.Loaded += (sender, args) =>
{
var manager = this.Container.Resolve<IRegionManager>();
manager.RequestNavigate("ContentRegion", "ViewA");
};
return window;
}
2. ViewModel decides the default view
Add a constructor to MainWindowViewModel that looks like this:
public MainWindowViewModel(IRegionManager regionManager)
{
regionManager.RegisterViewWithRegion("ContentRegion", "ViewA");
}
This really should be easy, but I could not establish it.
I have small WPF application with Prism 6
I have Main Window and two views inside it.
MainWindow with MainWindowViewModel view model class
ConfigurationView with ConfigurationViewModel view model class
SignInView with SignInViewModel view model class
Now when show the main window for the first time, I want to select which view to show according to some boolean condition
here is the snippet of the bootstrap class.
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.Register<MainWindow, MainWindowViewModel>();
ViewModelLocationProvider.Register<SignInView, SignInViewModel>();
ViewModelLocationProvider.Register<ConfigurationView, ConfigurationViewModel>();
Container.RegisterInstance(new SignInView());
Container.RegisterInstance(new ConfigurationView());
}
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
IRegionManager _regionManager = Container.Resolve<IRegionManager>();
IRegion _region = _regionManager.Regions[RegionNames.MainRegion];
_region.Add(Container.Resolve<SignInView>());
_region.Add(Container.Resolve<ConfigurationView>());
}
Right now always the SignInView is displayed when the main window of the application is opened?
How can I select which view to show according to some condition which need to be brought from the MainWindowViewModel class ?
Update
public class MainWindowViewModel
{
private IAccountService _accountService;
public MainWindowViewModel(IUnityContainer container)
{
IRegionManager regionManager = Container.Resolve<IRegionManager>();
_accountService = container.Resolve<IAccountService>();
if (_accountService.IsSignedIn)
regionManager.RequestNavigate(RegionNames.MainRegion, new Uri(nameof(ConfigurationView), UriKind.Relative));
else
regionManager.RequestNavigate(RegionNames.MainRegion, new Uri(nameof(SignInView), UriKind.Relative));
}
}
Main Window View
<Window x:Class="Shell.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
this code is not working.
The region needs to be created before you can add a view to it. So you'd better create the MainViewModel yourself after the MainWindow has been created:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.Register<SignInView, SignInViewModel>();
ViewModelLocationProvider.Register<ConfigurationView, ConfigurationViewModel>();
Container.RegisterInstance(new SignInView());
Container.RegisterInstance(new ConfigurationView());
}
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
IRegionManager _regionManager = Container.Resolve<IRegionManager>();
IRegion _region = _regionManager.Regions[RegionNames.MainRegion];
_region.Add(Container.Resolve<SignInView>());
_region.Add(Container.Resolve<ConfigurationView>());
var mainWindowViewModel = Container.Resolve<MainWindowViewModel>();
Application.Current.MainWindow.DataContext = mainWindowViewModel;
Application.Current.MainWindow.Show();
}
Remove this from the MainWindow:
prism:ViewModelLocator.AutoWireViewModel="True">
First of all, register the types of your views for navigation, not view instances.
Secondly: conditions - or more general: data - rarely originates from a view model (unless it's user input), so you should have some IUserManagement service to pull the current user from.
But that being said, if you have your view registered, you should be able to navigate to it. Without a concrete exception, it's difficult to guess what's going wrong.
Container.RegisterTypeForNavigation<SignInView>();
...
_regionManager.RequestNavigate( RegionNames.MainRegion, typeof(SignInView).Name );
I have a WPF application where I am using multiple forms. There is one main form which gets opened when we start the application which is know as MainWindow.xaml. This form then have multiple forms which gets opened depending on the user option. There is a form StartClassWindow.xaml. Currently I am working on this form so I want it to start directly instead of MainWindow.xaml. So to do this I changed the app.xaml startupuri:
<Application x:Class="Class.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DispatcherUnhandledException="Application_DispatcherUnhandledException"
StartupUri="StartClassWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
But then it started giving error like below:
No matching constructor found on type 'Class.StartClassWindow'. You
can use the Arguments or FactoryMethod directives to construct this
type.' Line number '3' and line position '9'.
Here is the StartClassWindow.xaml.cs:
namespace Class
{
public partial class StartClassWindow : System.Windows.Window
{
public StartClassWindow(string classData)
{
InitializeComponent();
className = classData;
function();
}
//rest of the code.
}
}
You need to add a parameter-less constructor to your StartClassWindow like this:
public StartClassWindow(string classData)
{
InitializeComponent();
className = classData;
function();
}
public StartClassWindow()
{
}
Or if you don't want to have another constructor you can override the OnStartup method in the App.xaml.cs but you should remove the StartupUri="StartClassWindow.xaml" in your App.xaml first. Like below:
protected override void OnStartup(StartupEventArgs e)
{
StartClassWindow st = new StartClassWindow("");
st.Show();
}
"Normally", your constructor must be parameterless:
public Login()
But, since you are using dependency injection, like this:
public Login(IUserService userService)
The constructor isn't parameterless, and the framework cannot instantiate the page if it's not told how to.
For this there are a couple of options:
Remove service from constructor
Just like this, but you'll need to access the userservice differently:
public Login()
pre .net 4.8, 4.7, using Unity (or Prism)
You can use a dependency injection framework like Unity to register the components.
It is described here:
https://www.wpftutorial.net/ReferenceArchitecture.html
public class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IUserService, UserService>();
MainWindow mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
}
Manual using Navigation Service
Manually navigate, and do your own construction:
NavigationService.Navigate(new LoginPage(new UserService);
As described here: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/app-development/navigation-overview?view=netframeworkdesktop-4.8
.net 5 WPF, using built in DI
If you are using .net 5, here is a tutorial. Make sure to register both the window and the service:
https://executecommands.com/dependency-injection-in-wpf-net-core-csharp/
Here's an example:
private void ConfigureServices(ServiceCollection services)
{
services.AddScoped<IUserService,UserService>();
services.AddSingleton<MainWindow>();
}
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
so I have inherited the development of a WPF application that uses Caliburn.Micro. I have been tasked to extend the application and include some AddIn functionality, I have coded the core logic for the AddIn feature which all works pretty well until I run the application and the shell view informs me that it "Cannot find view for TestViewModel"
Here is my Configure method in the bootstrapper
protected override void Configure()
{
this._log.Debug("-->AppBootstrapper.Configure[ENTER]");
try
{
SplashScreenForm.SplashScreen.Dispatcher.BeginInvoke(
(Action)(() => SplashScreenForm.SplashScreen.Message = "Initializing Container..."));
this._container = new CompositionContainer(new AggregateCatalog(new DirectoryCatalog(".", "*")));
var batch = new CompositionBatch();
SplashScreenForm.SplashScreen.Dispatcher.BeginInvoke(
(Action)(() => SplashScreenForm.SplashScreen.Message = "Initializing Dependencies..."));
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(this._container);
this._container.Compose(batch);
}
catch (Exception ex)
{
this._log.ErrorFormat("-->AppBootstrapper.Configure - {0}\n{1}", ex.Message, ex);
throw new Exception(ex.Message, ex);
}
this._log.Debug("-->AppBootstrapper.Configure[EXIT]");
}
I then have two assemblies, AppMain which contains the main application logic (this has a ViewModels and Views folders and these all load fine), I also have an AppAddinTest assembly which contains a test addin, this also contains a ViewModels and Views folder.
My TestViewModel code is:
[Export(typeof(TestViewModel))]
public class TestViewModel : BaseViewModel, ITestViewModel
{
private readonly IEventAggregator _eventAggregator;
private readonly IWindowManager _windowManager;
[ImportingConstructor]
public TestViewModel(IEventAggregator eventAggregator, IWindowManager windowManager)
{
this._eventAggregator = eventAggregator;
this._windowManager = windowManager;
}
}
And the TestView.xaml is:
<UserControl x:Class="Cleo.Windows.Ui.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="This is a test view from a different assembly!!"></TextBlock>
</Grid>
</UserControl>
Could anyone shed any light on what I have done wrong please and also why the application is unable to find the view?
I bet you haven't added the assembly on the stage of bootstrapping where your ViewModel resides.
Override SelectAssemblies() in the bootstrapper:
protected override IEnumerable<Assembly> SelectAssemblies() {
var assemblies = new List<Assembly> {
//AddTheAssemblyWhereViewModelsReside
};
return assemblies;
}