【问题标题】:Prism/WPF views/viewmodels being created twice棱镜/WPF 视图/视图模型被创建两次
【发布时间】:2020-10-03 05:16:43
【问题描述】:

我有一个 Prism 7 / WPF / MVVM 应用程序,该应用程序在具有视图模型的视图中配置为 AutowireViewModel="True"。我的大多数视图模型都具有我在 Prism Unity 容器中配置的依赖项,这些依赖项被注入到 viewmodel 构造函数中。我没有在任何地方的代码中明确创建视图/视图模型的实例。我也没有在 XAML 中设置数据上下文,即使用 DataContext 元素或 d:DataContext)。 XAML 中唯一的引用是视图(即 HamburgerMenu 控件的一部分)。

除了每个视图/视图模型由于某种原因总是被构建两次,无论是主窗口还是模块中的视图,一切都运行良好。我在模块管理器中放置了断点(只被命中一次) - 以及在视图构造函数和视图模型构造函数中都被命中两次。 以下是 App 类的一些代码以及名为 Messaging 的模块的一些代码/视图/视图模型,该模块在运行时加载,而不是按需加载。由于希望尽可能多地使用 MVVM 模式,我在视图后面的代码中几乎没有代码。注意:我试图查看这是否只是启动时加载的模块视图的问题,但按需加载的模块也存在相同的命运。

完整的 App/PrismApplication 类:

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();
    }
}

视图模型(调用基础消息视图模型类):

public sealed class MessagingMainViewModel
{
    public MessagingMainViewModel()
    {            
    }
}

更新:我已经从我的应用程序中删除了代码,因此只有上面的代码在视图和视图模型中。视图构造函数和视图模型构造函数在模块初始化期间仍然被命中两次。

【问题讨论】:

  • I am not entirely sure what code to post 至少是 MessagingMainView 的完整 xaml 以及它的代码隐藏。
  • 使用完整的 MessagingMainView(XAML 和代码隐藏)和完整的 MessagingMainViewModel 更新帖子。

标签: c# wpf mvvm constructor prism


【解决方案1】:

我找到了问题。就是 App CreateShell() 方法中的如下一行:

Container.GetContainer().RegisterInstance(typeof(Window), "MainWindow", Container.Resolve<MainWindow>(), new ContainerControlledLifetimeManager());

注释掉这个问题已经解决了。再看一遍我一直很傻,没有发现这一点,就像我是新手一样。添加了此代码,因此我可以访问服务类中的 MainWindow/MetroWindow - 但我需要以另一种方式尝试。感谢您的时间@Haukinger。

【讨论】:

    【解决方案2】:

    我没有在任何地方的代码中显式创建视图/视图模型的实例(即仅在 XAML 中)。

    这是一个矛盾 - 在 xaml 中创建视图模型意味着以与在代码后面或其他任何地方相同的方式创建视图模型。

    如前所述,不要这样做。也从 xaml 中删除对视图模型构造函数的所有引用(如 &lt;DataContext&gt;&lt;MyViewViewModel/&gt;&lt;/DataContext&gt;)。要在 xaml 中获取智能感知,请使用 d:DataContext

    【讨论】:

    • 嗨。抱歉,我的帖子不够清楚。重申/澄清,我没有在代码后面创建任何视图/视图模型,也没有在 XAML 中对 DataContext 的任何引用(即 或 d:DataContext)。我已经用完整的视图和视图模型代码更新了原始帖子。谢谢。
    猜你喜欢
    • 2021-09-07
    • 2019-06-12
    • 1970-01-01
    • 2019-10-18
    • 1970-01-01
    • 1970-01-01
    • 2017-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多