英文:
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 Community document link
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 methods by CommunityToolkit.Mvvm and bind them with buttons 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 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:
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.
答案1
得分: 1
当你想要使用WindowManager打开一个窗口时,你执行以下步骤:
-
你从DI容器中获取一个新的窗口实例
var window = _serviceProvider.GetRequiredService<TWindow>();
-
你显示这个新窗口实例
window.Show();
当你想要使用WindowManager关闭一个窗口时,你执行以下步骤:
-
你从DI容器中获取一个新的窗口实例
var window = _serviceProvider.GetRequiredService<TWindow>();
-
你关闭这个新窗口实例
window.Close();
而问题就在于这一点:每次你想关闭一个已经显示的窗口时,你都会创建一个新的实例,因为窗口被注册为瞬态(transient)。
英文:
When you want to open a window with the WindowManager you did
- You get a new window instance from DI-Container
var window = _serviceProvider.GetRequiredService<TWindow>();
- You show that new window instance
window.Show();
And when you want to close a window with the WindowManager you did
- You get a new window instance from DI-Container
var window = _serviceProvider.GetRequiredService<TWindow>();
- You close that new window instance
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
的字典,用来保存窗口类型和视图模型类型。
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($"未为视图模型类型 {viewModelType.FullName} 映射窗口类型");
}
这样,当我想要添加另一个窗口时,只需在 viewModelWindowMapping
中添加一个映射即可。
英文:
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
答案3
得分: 0
你应该关闭先前打开的窗口实例。
例如,你可以在你的 WindowManager
类中处理这个问题,通过在一个集合中跟踪当前打开的窗口,例如:
public class WindowManager : IWindowManager
{
private readonly IServiceProvider _serviceProvider;
private readonly List<Window> _openWindows = new List<Window>();
public WindowManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void OpenWindow<TWindow>() where TWindow : Window
{
var window = _serviceProvider.GetService<TWindow>();
if (window != null)
{
_openWindows.Add(window);
window.Show();
}
}
public void CloseWindow<TWindow>() where TWindow : Window
{
var window = _openWindows.OfType<TWindow>().FirstOrDefault();
if (window != null)
{
window.Close();
_openWindows.Remove(window);
}
}
}
英文:
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.:
public class WindowManager : IWindowManager
{
private readonly IServiceProvider _serviceProvider;
private readonly List<Window> _openWindows = List<Window>();
public WindowManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void OpenWindow<TWindow>() where TWindow : Window
{
var window = _serviceProvider.GetService<TWindow>();
if (window != null)
{
_openWindows.Add(window);
window.Show();
}
}
public void CloseWindow<TWindow>() where TWindow : Window
{
var window = _openWindows.OfType<TWindow>().FirstOrDefault();
if (window != null)
{
window.Close();
_openWindows.Remove(window);
}
}
}
答案4
得分: -1
我尝试重写MainWindow类中的CloseWindow
方法。
public partial class MainWindow : Window, IWindowManager
{
public MainWindow()
{
InitializeComponent();
}
public void CloseWindow() => Close();
}
以及MainWindowViewModel类中的按钮点击方法。
[RelayCommand]
private void CloseMainWindow(IWindowManager windowManager)
{
windowManager.CloseWindow();
}
它能够工作,但不符合我的要求。这意味着我将在每个视图中重写CloseWindow
方法。我的意图是通过一个接口来控制所有窗口的打开或关闭。
英文:
I treid to rewrite CloseWindow
method in MainWindow class.
public partial class MainWindow : Window, IWindowManager
{
public MainWindow()
{
InitializeComponent();
}
public void CloseWindow() => Close();
}
And Button Clicked methond in MainWindowViewModel class.
[RelayCommand]
private void CloseMainWindow(IWindowManager windowManager)
{
windowManager.CloseWindow();
}
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论