我有一个 WPF 应用程序。在左侧的主窗口中,是一个包含多个条目的列表框,右侧是一个 ContentControl,当选择其中一个条目时,应将 UserControl 以及视图模型加载到其中。
接下来,当选择列表框中的条目之一时,应创建一个具有视图模型的 UserControl 实例,列表框中选定的元素或其字段之一应传递给视图模型构造函数。
我不知道如何在不手动创建 UserControl 和视图模型的新实例的情况下正确执行此操作,而不违反 DI 原则,如果手动创建实例,则应用程序在关闭时不会从内存中清除。
MainView.Xaml
:
<ListBox ItemsSource="{Binding ContainerList}" SelectedItem="{Binding SelectedContainer}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding ShowContent}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
<ContentControl Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{Binding TheContent}"></ContentControl>
背后代码:
public partial class MainView : Window
{
public MainView(IMainViewModel viewModel)
{
this.DataContext = viewModel;
InitializeComponent();
}
}
public class MainViewModel : ViewModelBase, IMainViewModel
{
private readonly IRepositories _repositories;
private readonly IAbstractFactory<ChangeExecutorView> _factory;
public ObservableCollection<Container> ContainerList { get; set; }
private Container _SelectedContainer { get; set; }
public Container SelectedContainer { get { return _SelectedContainer; } set { _SelectedContainer = value; OnPropertyChanged(nameof(SelectedContainer)); }
}
private object _TheContent { get; set; }
public object TheContent
{
get { return _TheContent; }
set {_TheContent = value; OnPropertyChanged(nameof(TheContent)); }
}
// public MainViewModel(IContainerRepository repContainer, IAbstractFactory<ChangeExecutorView> factory)
public MainViewModel(IRepositories repositories, IAbstractFactory<ChangeExecutorView> factory)
{
_repositories = repositories;
_factory = factory;
ContainerList = new ObservableCollection<Container>(_repositories.ContainerRepository.GetAll());
}
// Here is action for create new UserControl
public ICommand ShowContent
{
get {
return new RelayCommand(delegate (object param)
{
// var content = new ContainerContentView(
// new ContainerContentViewModel(_repositories, SelectedUserID));
});
}
}
}
public interface IMainViewModel
{
ObservableCollection<Container> ContainerList { get; set; }
}
app.xaml
:
public static IHost AppHost { get; set; }
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureHostConfiguration((hostConfiguration => {
hostConfiguration.AddJsonFile("appsettings.json",false,true)
.AddEncryptedProvider()
.AddJsonFile($"appsettings.json", false, true);
}))
.ConfigureServices((hostConext, services) =>
{
services.AddSingleton<MainView>();
services.AddTransient<IMainViewModel, MainViewModel>();
services.AddTransient<IRepositories,Repositories>();
services.AddFormFactory<ChangeExecutorView>();
services.AddScoped<ContainerContentViewModel>();
})
.Build();
}
public static T GetService<T>() where T : class
{
var service = AppHost.Services.GetService(typeof(T)) as T;
return service;
}
protected override async void OnStartup(StartupEventArgs e)
{
await AppHost.StartAsync();//.ConfigureAwait(true);
var startupForm = AppHost.Services.GetRequiredService<MainView>();
startupForm.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
await AppHost.StopAsync();
base.OnExit(e);
}
}
改进代码的一些注意事项:
GetService<T>
不应该是 public static
,而应该是 private
或 private static
。将方法声明为 public 为从应用程序代码中引用容器打开了大门。如果您需要动态创建实例,请使用工厂(例如抽象工厂模式)。
不要仅使用
Interaction
触发器来对所选项目的更改做出反应。不需要让鼠标事件触发命令。在视图模型中处理属性更改会更直接。因为当 SelectedContainer
更改时 ListBox.SelectedItem
属性也已更改,因此您不需要额外的冗余通知。请参阅下面的示例。
您完整的财产申报是错误的。您当前正在定义一个
private
自动属性,并从 public
完整属性引用它,如下所示:
// Wrong: this is an auto property to backup the full property.
// Instead, the backup property must be a field.
private Container _selectedContainer { get; set; }
// Wrong: this property is backed by a property instead of a field
public Container SelectedContainer { get => _selectedContainer; } set => _selectedContainer = value; }
但是你必须使用支持fields来代替:
// Correct: the backing FIELD
private Container _selectedContainer;
// Correct: the full property that uses the backing FIELD
public Container SelectedContainer { get => _selectedContainer; } set => _selectedContainer = value; }
此外,您的类和符号名称具有误导性,因为 container 是 UI 上下文中使用的术语。在视图中,容器包装数据项,例如
ListBoxItem
是项目容器。以防万一:如果您正在视图模型中处理容器控件,那么这是错误的,可以而且应该避免。您的问题不清楚 Container
是什么。
标准 C# 命名约定要求字段名称为 camelCase 而不是 PascalCase,例如
private int _numericValue;
。你的命名风格看起来很奇怪。
回答你的问题:
通常,您不会在 C# 代码中创建控件。相反,您可以使用工厂动态创建数据模型,并使用
DataTemplate
让框架为您创建控件 - 为数据模型的每个实例创建一个控件。
MainViewModel
导入的工厂服务。您可以创建自定义抽象工厂以进行更复杂的创建(例如,当工厂方法包含许多参数或创建的实例需要额外配置时)或注册一个简单的Func<T>
(无参数或机智参数):ServiceProvider container = new ServiceCollection()
.AddSingleton<Func<ContainerContentViewModel>>(serviceProvider => serviceProvider.GetRequiredService<ContainerContentViewModel>)
.AddSingleton<IMainViewModel, MainViewModel>()
.BuildServiceProvider(new ServiceProviderOptions() { ValidateOnBuild = true });
// More examples to show how to register factories
// for more complex instance creation:
//
// If you need to register a factory that requires dynamic parameters
// (provided by the caller of the factory):
.AddSingleton<Func<string, int, ContainerContentViewModel>>(
serviceProvider => (name, number) => new ContainerContentViewModel(name, number))
// If you need to configure the instance created by the factory
// with dynamic parameters (provided by the caller of the factory):
.AddSingleton<Func<string, int, ContainerContentViewModel>>(serviceProvider => (name, number)
=>
{
var viewModel = serviceProvider.GetRequiredService<ContainerContentViewModel>();
viewModel.Name = name;
viewModel.Number = number;
return viewModel;
}
MainViewModel
中,并使用它来动态创建实例:MainViewModel.cs
public class MainViewModel : ViewModelBase, IMainViewModel
{
private readonly Func<ContainerContentViewModel> _containerContentViewModelFactory;
private Container _selectedContainer;
public Container SelectedContainer
{
get => _selectedContainer;
set
{
_selectedContainer = value;
OnPropertyChanged(nameof(SelectedContainer));
// Load the new view content by assigning a data model to
// the TheContent property.
// Use this variant over an InteractionBehavior and a ICommand
// (which were introduced only to react to property changes).
OnSelectedContainerChanged();
}
}
private object _theContent;
public object TheContent
{
get => _theContent;
set
{
_theContent = value;
OnPropertyChanged(nameof(TheContent));
}
}
// Declare the factory as constructor dependency
public MainViewModel(Func<ContainerContentViewModel> containerContentViewModelFactory)
{
_containerContentViewModelFactory = containerContentViewModelFactory;
}
// Here is action for create new UserControl
public void OnSelectedContainerChanged()
{
ContainerContentViewModel content = _containerContentViewModelFactory.Invoke();
// The property will update the ContentControl
// which is using a DataTemplate to load the actual control
TheContent = content;
}
}
MainView.xaml
<Window>
<Window.Resources>
<!--
The implicit DataTemplate that is automatically loaded
by the ContentControl
-->
<DataTemplate DataType="{x:Type ContainerContentViewModel}">
<ContainerContentView />
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding ContainerList}"
SelectedItem="{Binding SelectedContainer}" />
<ContentControl Content="{Binding TheContent}" />
</Grid>
</Window>