Search code examples
c#wpfmvvmioc-container

When i use IoC container to control windows in WPF MVVM, Open Window workes, but Close Window doesn't work


I want to create a Interface to control all windows. I used Microsoft.Extensions.DependencyInjection .

Interface IWindowManager and class WindowManager as:

public interface IWindowManager
    {
        void OpenWindow<TWindow>() where TWindow : Window;
        void CloseWindow<TWindow>() where TWindow : Window;
    }
public class WindowManager : IWindowManager
    {
        private readonly IServiceProvider _serviceProvider;

        public WindowManager(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public void OpenWindow<TWindow>() where TWindow : Window
        {
            var window = _serviceProvider.GetRequiredService<TWindow>();
            window.Show();
        }

        public void CloseWindow<TWindow>() where TWindow : Window
        {
            var window = _serviceProvider.GetRequiredService<TWindow>();
            window.Close();
        }
    }

In App.xaml.cs, i create IoC container, it's the same as the code of Microsoft Commuinty document https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/ioc

public partial class App : Application
    {
        public App() 
        {
            Services = ConfigureServices();
        }

        public new static App Current => (App)Application.Current;

        public IServiceProvider Services { get; }

        private static IServiceProvider ConfigureServices()
        {
            var services = new ServiceCollection();
            services.AddSingleton<IWindowManager, WindowManager>();

            services.AddTransient<MainWindowViewModel>();
            services.AddTransient<MainWindow>(
                sp => new MainWindow
                { DataContext = sp.GetService<MainWindowViewModel>() });

            services.AddTransient<TaskWindowViewModel>();
            services.AddTransient<TaskWindow>(
                sp => new TaskWindow
                { DataContext = sp.GetService<TaskWindowViewModel>() });

            return services.BuildServiceProvider();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            var MW = Services.GetService<MainWindow>();
            MW!.Show();
        }
    }

In MainWindowViewModel.cs, i create two RelayCommand method by CommunityToolkit.Mvvm, and binding with button in Xaml:

 public partial class MainWindowViewModel : ObservableObject
    {
        private readonly IWindowManager _windowManager;

        #region Windows Manager
        [RelayCommand]
        private void OpenTaskWindow()
        {
            _windowManager.OpenWindow<TaskWindow>();
        }

        [RelayCommand]
        private void CloseMainWindow()
        {
            _windowManager.CloseWindow<MainWindow>();
        }
        #endregion

        public MainWindowViewModel( IWindowManager windowManager)
        {
            _windowManager = windowManager;
        }
    }

I tried the method of OpenTaskWindow workes, but CloseMainWindow does't work. I don't know what's wrong.Please help me, thank you.


Solution

  • I resolved it. Add a dictionary viewModelWindowMapping to save types of windows and viewModels.

        private Dictionary<object, Window> windowList = new();
    
        private Dictionary<Type, Type> viewModelWindowMapping = new()
        {
            { typeof(MainWindowViewModel),typeof(MainWindow)},
            { typeof(TaskWindowViewModel),typeof(TaskWindow)}
        };
    
        public WindowManager(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
    
        public void OpenWindow(object viewModel)
        {
            if (viewModel == null)
            {
                throw new ArgumentNullException(nameof(viewModel));
            }
    
            if (windowList.ContainsKey(viewModel))
            {
                return;
            }
    
            Window window = GetWindowForViewModel(viewModel);
    
            windowList[viewModel] = window;
    
            window.Show();
        }
    
        public void CloseWindow(object viewModel)
        {
            if (viewModel == null)
            {
                throw new ArgumentNullException(nameof(viewModel));
            }
    
            if (!windowList.ContainsKey(viewModel))
            {
                return;
            }
    
            Window window = windowList[viewModel];
    
            window.Close();
    
            windowList.Remove(viewModel);
        }
    
        private Window GetWindowForViewModel(object viewModel)
        {
            var windowType = GetWindowTypeForViewModel(viewModel.GetType());
            return (Window)_serviceProvider.GetRequiredService(windowType);
        }
    
        private Type GetWindowTypeForViewModel(Type viewModelType)
        {
            if (viewModelWindowMapping.TryGetValue(viewModelType, out var windowType))
            {
                return windowType;
            }
            throw new Exception($"No window type is mapped for the view model type {viewModelType.FullName}");
        }
    

    Like this, when i want to add another window. I just need add one mapping in viewModelWindowMapping