Prism/WPF views/viewmodels 被创建两次
Prism/WPF views/viewmodels being created twice
我有一个 Prism 7/WPF/MVVM 应用程序,它在具有视图模型的视图中配置了 AutowireViewModel="True"。我的大多数视图模型都具有我在 Prism Unity 容器中配置的依赖项,这些依赖项被注入到视图模型构造函数中。我没有在任何地方的代码中明确创建 views/viewmodels 的实例。我也没有在 XAML 中设置数据上下文,即使用 DataContext 元素或 d:DataContext)。 XAML 中唯一的引用是视图(即 HamburgerMenu 控件的一部分)。
除了每个 view/viewmodel 总是出于某种原因构建两次外,一切正常,无论这是主 window 还是模块中的视图。我在模块管理器中放置了断点(只被命中一次) - 以及视图构造函数和视图模型构造函数中都被命中了两次。
以下是 App class 的一些代码,以及在运行时而非按需加载的名为 Messaging 的模块的一些代码/view/viewmodel。由于希望尽可能使用MVVM模式,我在视图的代码后面的代码很少。注意:我试图查看这是否只是启动时加载的模块视图的问题,但按需加载的模块也存在同样的命运。
App/PrismApplicationclass全文:
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;
}
}
消息模块:
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>();
}
}
}
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>
后面的代码:
public sealed partial class MessagingMainView : UserControl
{
public MessagingMainView()
{
this.InitializeComponent();
}
}
视图模型(调用基本消息视图模型class):
public sealed class MessagingMainViewModel
{
public MessagingMainViewModel()
{
}
}
更新:我已经从我的应用程序中删除了代码,因此只有上面的代码在视图和视图模型中。视图构造函数和视图模型构造函数在模块初始化期间仍然被命中两次。
I do not explicitly create instances of views/viewmodels in the code behind anywhere (i.e. only in XAML).
这是一个矛盾 - 在 xaml 中创建视图模型意味着以与在代码隐藏或其他任何地方相同的方式创建视图模型。
如前所述,只是不要这样做。也从 xaml 中删除对视图模型构造函数的所有引用(如 <DataContext><MyViewViewModel/></DataContext>
)。要在 xaml 中获得智能感知,请使用 d:DataContext
.
我找到问题了。 App CreateShell() 方法中的以下行:
Container.GetContainer().RegisterInstance(typeof(Window), "MainWindow", Container.Resolve<MainWindow>(), new ContainerControlledLifetimeManager());
将此注释掉已解决问题。再看看它,我很愚蠢,没有发现这一点,就像我是新手一样。添加此代码是为了让我可以访问服务 class 中的 MainWindow/MetroWindow - 但我需要以其他方式尝试。感谢您花时间@Haukinger。
我有一个 Prism 7/WPF/MVVM 应用程序,它在具有视图模型的视图中配置了 AutowireViewModel="True"。我的大多数视图模型都具有我在 Prism Unity 容器中配置的依赖项,这些依赖项被注入到视图模型构造函数中。我没有在任何地方的代码中明确创建 views/viewmodels 的实例。我也没有在 XAML 中设置数据上下文,即使用 DataContext 元素或 d:DataContext)。 XAML 中唯一的引用是视图(即 HamburgerMenu 控件的一部分)。
除了每个 view/viewmodel 总是出于某种原因构建两次外,一切正常,无论这是主 window 还是模块中的视图。我在模块管理器中放置了断点(只被命中一次) - 以及视图构造函数和视图模型构造函数中都被命中了两次。 以下是 App class 的一些代码,以及在运行时而非按需加载的名为 Messaging 的模块的一些代码/view/viewmodel。由于希望尽可能使用MVVM模式,我在视图的代码后面的代码很少。注意:我试图查看这是否只是启动时加载的模块视图的问题,但按需加载的模块也存在同样的命运。
App/PrismApplicationclass全文:
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;
}
}
消息模块:
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>();
}
}
}
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>
后面的代码:
public sealed partial class MessagingMainView : UserControl
{
public MessagingMainView()
{
this.InitializeComponent();
}
}
视图模型(调用基本消息视图模型class):
public sealed class MessagingMainViewModel
{
public MessagingMainViewModel()
{
}
}
更新:我已经从我的应用程序中删除了代码,因此只有上面的代码在视图和视图模型中。视图构造函数和视图模型构造函数在模块初始化期间仍然被命中两次。
I do not explicitly create instances of views/viewmodels in the code behind anywhere (i.e. only in XAML).
这是一个矛盾 - 在 xaml 中创建视图模型意味着以与在代码隐藏或其他任何地方相同的方式创建视图模型。
如前所述,只是不要这样做。也从 xaml 中删除对视图模型构造函数的所有引用(如 <DataContext><MyViewViewModel/></DataContext>
)。要在 xaml 中获得智能感知,请使用 d:DataContext
.
我找到问题了。 App CreateShell() 方法中的以下行:
Container.GetContainer().RegisterInstance(typeof(Window), "MainWindow", Container.Resolve<MainWindow>(), new ContainerControlledLifetimeManager());
将此注释掉已解决问题。再看看它,我很愚蠢,没有发现这一点,就像我是新手一样。添加此代码是为了让我可以访问服务 class 中的 MainWindow/MetroWindow - 但我需要以其他方式尝试。感谢您花时间@Haukinger。