I have a WPF application. In the main window on the left is a listbox with several entries, and on the right is a ContentControl into which, when selecting one of the entries, the UserControl along with the view model should be loaded.
Next, when selecting one of the entries in the listbox, a UserControl instance with a view model should be created, the selected element from the listbox or one of its fields should be passed to the view model constructor.
I do not know how to do this correctly without creating a new instance of the UserControl and view model manually, without violating the principles of DI, if you create an instance manually, then the application is not cleared from memory when closed.
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>
Code behind:
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);
}
}
Some considerations to improve your code:
GetService<T>
should not be public static
but private
or private static
. Declaring the method as public opens the door for referencing the container from your application code. If you need to create instances dynamically use a factory (e.g. Abstract Factory pattern).
Don't use an Interaction
trigger only to react on selected item changes. there is no need to let the mouse event trigger a command. It would be much straighter to handle the property change in the view model. Because the SelectedContainer
property is already changing when ListBox.SelectedItem
changes you don't need an additional and therefore redundant notification. See the below example.
You full property declarations are wrong. You are currently defining a private
auto property and reference it from the public
full property like this:
// 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; }
But you must use backing fields instead:
// Correct: the backing FIELD
private Container _selectedContainer;
// Correct: the full property that uses the backing FIELD
public Container SelectedContainer { get => _selectedContainer; } set => _selectedContainer = value; }
Also, your class and symbol names are misleading as container is a term used in context of UI. In the view, a container wraps the data item e.g., ListBoxItem
is a item container. Just in case: if you are handling container controls in your view model, then this is wrong and can and should be avoided. It's not clear form your question what Container
is.
The standard C# naming convention requires field names to be camelCase and not PascalCase e.g. private int _numericValue;
. Your naming style looks strange.
To answer your question:
In general, you don't create controls in C# code. Instead, you would dynamically create a data model using a factory and use a DataTemplate
to let the framework create the control for you - one for each instance of the data model.
MainViewModel
. You can create a custom abstract factory for more complex creation (e.g. when the factory method contains many parameters or the created instance requires additional configuration) or register a simple Func<T>
(parameterless or with parameters):ServiceProvider container = new ServiceCollection()
.AddSingleton<ContainerContentViewModel>()
.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
using the constructor and use it to dynamically create instances: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>