AvaloniaUI - 如何使用基于组合根的 DI 系统将 ViewModel 注入 View 中?

9

我对Avalonia/WPF、Xaml和桌面开发都很陌生,请谅解,如有误解请指正。我将继续学习相关文档,但是在找到能解决我的问题的材料方面遇到了困难。

我正在尝试在我的Avalonia应用程序中实现一个基于组合根、构造函数注入的依赖注入系统,使用推荐的MVVM模式和相应的Avalonia项目模板。我对Microsoft.Extensions.DependencyInjection包有一定的熟悉度,因此一直在尝试使用这个系统。

通过基于这个DI框架以及其他框架的教程,我试图拼凑出一个可行的解决方案。我认为我已经理解了如何注册服务和ViewModel,并适当地设置这些类的构造函数,以便框架会在实例化这些类时注入依赖项。然而,我卡在了如何为View类实现构造函数注入上。

我尝试将MainWindow和MainWindowViewModel都注册为服务:

// App.axaml.cs
public partial class App : Application
    {
        private IServiceProvider _services;
        
        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        
        public override void OnFrameworkInitializationCompleted()
        {
            ConfigureServiceProvider();

            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                desktop.MainWindow = _services.GetService<MainWindow>();
            }
            
            base.OnFrameworkInitializationCompleted();
        }
        
        
        private void ConfigureServiceProvider()
        {
            var services = ConfigureServices();
            _services = services.BuildServiceProvider();
        }
        
        private static IServiceCollection ConfigureServices()
        {
            var services = new ServiceCollection();
            
            services.AddTransient<MainWindow>();
            services.AddTransient<MainWindowViewModel>();

            return services;
        }
    }

目标是通过构造函数将MainWindowViewModel类注入到MainWindow类中,然后将该参数分配给MainWindow视图类的DataContext属性:
// MainWindow.axaml.cs
public partial class MainWindow : Window
    {
        public MainWindow(MainWindowViewModel viewModel)
        {
            DataContext = viewModel;
            InitializeComponent();
#if DEBUG
            this.AttachDevTools();
#endif
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }


然而,这会引发以下错误:
  MainWindow.axaml(1, 2): [XAMLIL] Unable to find public constructor for type MyApp.Client:MyApp.Client.Views.MainWindow() Line 1, position 2.

看起来没有无参构造函数的情况下无法实例化View,但这似乎会阻止构造函数注入。

我可能对ViewModels和Views之间预期的关系有些基本误解。我遇到了一些示例,其中ViewModels未在服务容器中注册,而是直接在View构造函数中实例化并分配给DataContext属性。我希望避免这种方法。

同时,我遇到的每个教程都演示了将ViewModels通过构造函数注入到相应的View类中的方法,但它们都使用了服务定位器模式,其中DI服务容器被显式传递(或作为全局对象调用),并且ViewModels从容器中显式地解析出来。

请问是否有人可以提供任何演示如何通过构造函数正确注入ViewModels到Views的示例源代码或教程?这可行吗?是否可以修改MainWindow.axaml文件以启用所需行为?谢谢您的时间,再次感谢向我澄清可能存在的误解。

仅供参考,此处是MainWindow的标记:

// MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:MyApp.Client.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="MyApp.Client.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        x:CompileBindings="True"
        Icon="/Assets/avalonia-logo.ico"
        Title="MyApp">

    <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>

</Window>
1个回答

8

视图模型通过 DataContext 与视图关联,而不是通过构造函数注入。请注意,单个视图可以是可重用的(特别是如果您正在处理虚拟列表)。

一般来说,您的 DI 不应该完全了解视图部分,它只应该关注 ViewModel 和更低层次。

视图通常不是通过 DI 创建的,而是通过其他将特定属性绑定到 ContentControl 的视图查找器定位的,例如:

<ContentControl Content="{Binding MySubViewModel} />

您可以在avalonia.mvvm模板中找到一个简单的视图定位器,您可以根据需要对其进行调整。

当需要从您的视图模型代码显示新的顶级视图时,通常会实现一些窗口管理器来管理顶级视图,并且可以通过DI从视图模型访问它,例如:

    public class ViewManager : IViewManager
    {
        private Window CreateWindowForModel(object model)
        {
            foreach (var template in Application.Current.DataTemplates)
            {
                if (template.Match(model))
                {
                    var control = template.Build(model);
                    if (control is Window w)
                        return w;
                    return new Window { Content = control };
                }
            }

            throw new KeyNotFoundException("Unable to find view for model: " + model);
        }

        public void ShowWindow(object model) => CreateWindowForModel(model).Show();
    }

然后您将IViewManager的实现添加到您的DI中。

请注意,这种方法可以在所有XAML框架中重复使用,并且使得在各种平台之间(例如如果您想使用Xamarin实现移动UI,而使用Avalonia实现桌面)只需使用一些UI工具包特定的服务即可完全重用视图模型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接