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

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

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&lt;TWindow&gt;() where TWindow : Window;
        void CloseWindow&lt;TWindow&gt;() where TWindow : Window;
    }
public class WindowManager : IWindowManager
    {
        private readonly IServiceProvider _serviceProvider;

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

        public void OpenWindow&lt;TWindow&gt;() where TWindow : Window
        {
            var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
            window.Show();
        }

        public void CloseWindow&lt;TWindow&gt;() where TWindow : Window
        {
            var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
            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 =&gt; (App)Application.Current;

        public IServiceProvider Services { get; }

        private static IServiceProvider ConfigureServices()
        {
            var services = new ServiceCollection();
            services.AddSingleton&lt;IWindowManager, WindowManager&gt;();

            services.AddTransient&lt;MainWindowViewModel&gt;();
            services.AddTransient&lt;MainWindow&gt;(
                sp =&gt; new MainWindow
                { DataContext = sp.GetService&lt;MainWindowViewModel&gt;() });

            services.AddTransient&lt;TaskWindowViewModel&gt;();
            services.AddTransient&lt;TaskWindow&gt;(
                sp =&gt; new TaskWindow
                { DataContext = sp.GetService&lt;TaskWindowViewModel&gt;() });

            return services.BuildServiceProvider();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            var MW = Services.GetService&lt;MainWindow&gt;();
            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&lt;TaskWindow&gt;();
        }

        [RelayCommand]
        private void CloseMainWindow()
        {
            _windowManager.CloseWindow&lt;MainWindow&gt;();
        }
        #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打开一个窗口时,你执行以下步骤:

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

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

    window.Show();
    

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

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

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

    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
    var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
    
  2. You show that new window instance
    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
    var window = _serviceProvider.GetRequiredService&lt;TWindow&gt;();
    
  2. 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&lt;object, Window&gt; windowList = new();

    private Dictionary&lt;Type, Type&gt; 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($&quot;No window type is mapped for the view model type {viewModelType.FullName}&quot;);
    }

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&lt;Window&gt; _openWindows = List&lt;Window&gt;();

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

    public void OpenWindow&lt;TWindow&gt;() where TWindow : Window
    {
        var window = _serviceProvider.GetService&lt;TWindow&gt;();
        if (window != null)
        {
            _openWindows.Add(window);
            window.Show();
        }
    }

    public void CloseWindow&lt;TWindow&gt;() where TWindow : Window
    {
        var window = _openWindows.OfType&lt;TWindow&gt;().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() =&gt; 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.

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:

确定