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

huangapple go评论104阅读模式
英文:

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:

  1. public interface IWindowManager
  2. {
  3. void OpenWindow<TWindow>() where TWindow : Window;
  4. void CloseWindow<TWindow>() where TWindow : Window;
  5. }
  1. public class WindowManager : IWindowManager
  2. {
  3. private readonly IServiceProvider _serviceProvider;
  4. public WindowManager(IServiceProvider serviceProvider)
  5. {
  6. _serviceProvider = serviceProvider;
  7. }
  8. public void OpenWindow<TWindow>() where TWindow : Window
  9. {
  10. var window = _serviceProvider.GetRequiredService<TWindow>();
  11. window.Show();
  12. }
  13. public void CloseWindow<TWindow>() where TWindow : Window
  14. {
  15. var window = _serviceProvider.GetRequiredService<TWindow>();
  16. window.Close();
  17. }
  18. }

In App.xaml.cs, I create IoC container, it's the same as the code of Microsoft Community document link

  1. public partial class App : Application
  2. {
  3. public App()
  4. {
  5. Services = ConfigureServices();
  6. }
  7. public new static App Current => (App)Application.Current;
  8. public IServiceProvider Services { get; }
  9. private static IServiceProvider ConfigureServices()
  10. {
  11. var services = new ServiceCollection();
  12. services.AddSingleton<IWindowManager, WindowManager>();
  13. services.AddTransient<MainWindowViewModel>();
  14. services.AddTransient<MainWindow>(
  15. sp => new MainWindow
  16. { DataContext = sp.GetService<MainWindowViewModel>() });
  17. services.AddTransient<TaskWindowViewModel>();
  18. services.AddTransient<TaskWindow>(
  19. sp => new TaskWindow
  20. { DataContext = sp.GetService<TaskWindowViewModel>() });
  21. return services.BuildServiceProvider();
  22. }
  23. private void Application_Startup(object sender, StartupEventArgs e)
  24. {
  25. var MW = Services.GetService<MainWindow>();
  26. MW!.Show();
  27. }
  28. }

In MainWindowViewModel.cs, I create two RelayCommand methods by CommunityToolkit.Mvvm and bind them with buttons in Xaml:

  1. public partial class MainWindowViewModel : ObservableObject
  2. {
  3. private readonly IWindowManager _windowManager;
  4. #region Windows Manager
  5. [RelayCommand]
  6. private void OpenTaskWindow()
  7. {
  8. _windowManager.OpenWindow<TaskWindow>();
  9. }
  10. [RelayCommand]
  11. private void CloseMainWindow()
  12. {
  13. _windowManager.CloseWindow<MainWindow>();
  14. }
  15. #endregion
  16. public MainWindowViewModel(IWindowManager windowManager)
  17. {
  18. _windowManager = windowManager;
  19. }
  20. }

I tried the OpenTaskWindow method, and it works, but CloseMainWindow doesn't work. I don't know what's wrong. Please help me. Thank you.

英文:

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

Interface IWindowManager and class WindowManager as:

  1. public interface IWindowManager
  2. {
  3. void OpenWindow&lt;TWindow&gt;() where TWindow : Window;
  4. void CloseWindow&lt;TWindow&gt;() where TWindow : Window;
  5. }
  1. public class WindowManager : IWindowManager
  2. {
  3. private readonly IServiceProvider _serviceProvider;
  4. public WindowManager(IServiceProvider serviceProvider)
  5. {
  6. _serviceProvider = serviceProvider;
  7. }
  8. public void OpenWindow&lt;TWindow&gt;() where TWindow : Window
  9. {
  10. var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
  11. window.Show();
  12. }
  13. public void CloseWindow&lt;TWindow&gt;() where TWindow : Window
  14. {
  15. var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
  16. window.Close();
  17. }
  18. }

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

  1. public partial class App : Application
  2. {
  3. public App()
  4. {
  5. Services = ConfigureServices();
  6. }
  7. public new static App Current =&gt; (App)Application.Current;
  8. public IServiceProvider Services { get; }
  9. private static IServiceProvider ConfigureServices()
  10. {
  11. var services = new ServiceCollection();
  12. services.AddSingleton&lt;IWindowManager, WindowManager&gt;();
  13. services.AddTransient&lt;MainWindowViewModel&gt;();
  14. services.AddTransient&lt;MainWindow&gt;(
  15. sp =&gt; new MainWindow
  16. { DataContext = sp.GetService&lt;MainWindowViewModel&gt;() });
  17. services.AddTransient&lt;TaskWindowViewModel&gt;();
  18. services.AddTransient&lt;TaskWindow&gt;(
  19. sp =&gt; new TaskWindow
  20. { DataContext = sp.GetService&lt;TaskWindowViewModel&gt;() });
  21. return services.BuildServiceProvider();
  22. }
  23. private void Application_Startup(object sender, StartupEventArgs e)
  24. {
  25. var MW = Services.GetService&lt;MainWindow&gt;();
  26. MW!.Show();
  27. }
  28. }

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

  1. public partial class MainWindowViewModel : ObservableObject
  2. {
  3. private readonly IWindowManager _windowManager;
  4. #region Windows Manager
  5. [RelayCommand]
  6. private void OpenTaskWindow()
  7. {
  8. _windowManager.OpenWindow&lt;TaskWindow&gt;();
  9. }
  10. [RelayCommand]
  11. private void CloseMainWindow()
  12. {
  13. _windowManager.CloseWindow&lt;MainWindow&gt;();
  14. }
  15. #endregion
  16. public MainWindowViewModel( IWindowManager windowManager)
  17. {
  18. _windowManager = windowManager;
  19. }
  20. }

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

答案1

得分: 1

当你想要使用WindowManager打开一个窗口时,你执行以下步骤:

  1. 你从DI容器中获取一个新的窗口实例

    1. var window = _serviceProvider.GetRequiredService<TWindow>();
  2. 你显示这个新窗口实例

    1. window.Show();

当你想要使用WindowManager关闭一个窗口时,你执行以下步骤:

  1. 你从DI容器中获取一个新的窗口实例

    1. var window = _serviceProvider.GetRequiredService<TWindow>();
  2. 你关闭这个新窗口实例

    1. window.Close();

而问题就在于这一点:每次你想关闭一个已经显示的窗口时,你都会创建一个新的实例,因为窗口被注册为瞬态(transient)

英文:

When you want to open a window with the WindowManager you did

  1. You get a new window instance from DI-Container
    1. var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
  2. You show that new window instance
    1. window.Show();

And when you want to close a window with the WindowManager you did

  1. You get a new window instance from DI-Container
    1. var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
  2. You close that new window instance
    1. window.Close();

And that is what is wrong: You always create a new instance - because the window is registered as transient - to close it when you want to close a window that is already shown.

答案2

得分: 1

我解决了它。添加一个名为 viewModelWindowMapping 的字典,用来保存窗口类型和视图模型类型。

  1. private Dictionary<object, Window> windowList = new();
  2. private Dictionary<Type, Type> viewModelWindowMapping = new()
  3. {
  4. { typeof(MainWindowViewModel), typeof(MainWindow) },
  5. { typeof(TaskWindowViewModel), typeof(TaskWindow) }
  6. };
  7. public WindowManager(IServiceProvider serviceProvider)
  8. {
  9. _serviceProvider = serviceProvider;
  10. }
  11. public void OpenWindow(object viewModel)
  12. {
  13. if (viewModel == null)
  14. {
  15. throw new ArgumentNullException(nameof(viewModel));
  16. }
  17. if (windowList.ContainsKey(viewModel))
  18. {
  19. return;
  20. }
  21. Window window = GetWindowForViewModel(viewModel);
  22. windowList[viewModel] = window;
  23. window.Show();
  24. }
  25. public void CloseWindow(object viewModel)
  26. {
  27. if (viewModel == null)
  28. {
  29. throw new ArgumentNullException(nameof(viewModel));
  30. }
  31. if (!windowList.ContainsKey(viewModel))
  32. {
  33. return;
  34. }
  35. Window window = windowList[viewModel];
  36. window.Close();
  37. windowList.Remove(viewModel);
  38. }
  39. private Window GetWindowForViewModel(object viewModel)
  40. {
  41. var windowType = GetWindowTypeForViewModel(viewModel.GetType());
  42. return (Window)_serviceProvider.GetRequiredService(windowType);
  43. }
  44. private Type GetWindowTypeForViewModel(Type viewModelType)
  45. {
  46. if (viewModelWindowMapping.TryGetValue(viewModelType, out var windowType))
  47. {
  48. return windowType;
  49. }
  50. throw new Exception($"未为视图模型类型 {viewModelType.FullName} 映射窗口类型");
  51. }

这样,当我想要添加另一个窗口时,只需在 viewModelWindowMapping 中添加一个映射即可。

英文:

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

  1. private Dictionary&lt;object, Window&gt; windowList = new();
  2. private Dictionary&lt;Type, Type&gt; viewModelWindowMapping = new()
  3. {
  4. { typeof(MainWindowViewModel),typeof(MainWindow)},
  5. { typeof(TaskWindowViewModel),typeof(TaskWindow)}
  6. };
  7. public WindowManager(IServiceProvider serviceProvider)
  8. {
  9. _serviceProvider = serviceProvider;
  10. }
  11. public void OpenWindow(object viewModel)
  12. {
  13. if (viewModel == null)
  14. {
  15. throw new ArgumentNullException(nameof(viewModel));
  16. }
  17. if (windowList.ContainsKey(viewModel))
  18. {
  19. return;
  20. }
  21. Window window = GetWindowForViewModel(viewModel);
  22. windowList[viewModel] = window;
  23. window.Show();
  24. }
  25. public void CloseWindow(object viewModel)
  26. {
  27. if (viewModel == null)
  28. {
  29. throw new ArgumentNullException(nameof(viewModel));
  30. }
  31. if (!windowList.ContainsKey(viewModel))
  32. {
  33. return;
  34. }
  35. Window window = windowList[viewModel];
  36. window.Close();
  37. windowList.Remove(viewModel);
  38. }
  39. private Window GetWindowForViewModel(object viewModel)
  40. {
  41. var windowType = GetWindowTypeForViewModel(viewModel.GetType());
  42. return (Window)_serviceProvider.GetRequiredService(windowType);
  43. }
  44. private Type GetWindowTypeForViewModel(Type viewModelType)
  45. {
  46. if (viewModelWindowMapping.TryGetValue(viewModelType, out var windowType))
  47. {
  48. return windowType;
  49. }
  50. throw new Exception($&quot;No window type is mapped for the view model type {viewModelType.FullName}&quot;);
  51. }

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

答案3

得分: 0

你应该关闭先前打开的窗口实例。

例如,你可以在你的 WindowManager 类中处理这个问题,通过在一个集合中跟踪当前打开的窗口,例如:

  1. public class WindowManager : IWindowManager
  2. {
  3. private readonly IServiceProvider _serviceProvider;
  4. private readonly List<Window> _openWindows = new List<Window>();
  5. public WindowManager(IServiceProvider serviceProvider)
  6. {
  7. _serviceProvider = serviceProvider;
  8. }
  9. public void OpenWindow<TWindow>() where TWindow : Window
  10. {
  11. var window = _serviceProvider.GetService<TWindow>();
  12. if (window != null)
  13. {
  14. _openWindows.Add(window);
  15. window.Show();
  16. }
  17. }
  18. public void CloseWindow<TWindow>() where TWindow : Window
  19. {
  20. var window = _openWindows.OfType<TWindow>().FirstOrDefault();
  21. if (window != null)
  22. {
  23. window.Close();
  24. _openWindows.Remove(window);
  25. }
  26. }
  27. }
英文:

You should close a window instance that has previously been opened.

You could for example handle this in your WindowManager class by keeping track of the currently opened windows in a collection, e.g.:

  1. public class WindowManager : IWindowManager
  2. {
  3. private readonly IServiceProvider _serviceProvider;
  4. private readonly List&lt;Window&gt; _openWindows = List&lt;Window&gt;();
  5. public WindowManager(IServiceProvider serviceProvider)
  6. {
  7. _serviceProvider = serviceProvider;
  8. }
  9. public void OpenWindow&lt;TWindow&gt;() where TWindow : Window
  10. {
  11. var window = _serviceProvider.GetService&lt;TWindow&gt;();
  12. if (window != null)
  13. {
  14. _openWindows.Add(window);
  15. window.Show();
  16. }
  17. }
  18. public void CloseWindow&lt;TWindow&gt;() where TWindow : Window
  19. {
  20. var window = _openWindows.OfType&lt;TWindow&gt;().FirstOrDefault();
  21. if (window != null)
  22. {
  23. window.Close();
  24. _openWindows.Remove(window);
  25. }
  26. }
  27. }

答案4

得分: -1

我尝试重写MainWindow类中的CloseWindow方法。

  1. public partial class MainWindow : Window, IWindowManager
  2. {
  3. public MainWindow()
  4. {
  5. InitializeComponent();
  6. }
  7. public void CloseWindow() => Close();
  8. }

以及MainWindowViewModel类中的按钮点击方法。

  1. [RelayCommand]
  2. private void CloseMainWindow(IWindowManager windowManager)
  3. {
  4. windowManager.CloseWindow();
  5. }

它能够工作,但不符合我的要求。这意味着我将在每个视图中重写CloseWindow方法。我的意图是通过一个接口来控制所有窗口的打开或关闭。

英文:

I treid to rewrite CloseWindow method in MainWindow class.

  1. public partial class MainWindow : Window, IWindowManager
  2. {
  3. public MainWindow()
  4. {
  5. InitializeComponent();
  6. }
  7. public void CloseWindow() =&gt; Close();
  8. }

And Button Clicked methond in MainWindowViewModel class.

  1. [RelayCommand]
  2. private void CloseMainWindow(IWindowManager windowManager)
  3. {
  4. windowManager.CloseWindow();
  5. }

It works, but not what i want.That means i will overrid CloseWindow method in every View.And my intention is to control the Opening or Closing of all windows through an interface.

huangapple
  • 本文由 发表于 2023年6月26日 14:09:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76553926.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定