We already talked about how to use Dependency Injection and Service Provider with WPF on .NET Core 3.0. In that occasion we said that windows themselves are registered in the IoC, so that we need to use the ServiceProvider to get and load them, for example:

var mainWindow = ServiceProvider.GetRequiredService<MainWindow>(); mainWindow.Show();

This approach works correctly, but we need to use two instructions every time we want to open a new window. Moreover, we don’t have a straight way to pass parameters to the new window. To make things simpler, we can write a couple of classes to manage this task:

public interface IActivable { Task ActivateAsync(object parameter); } public class SimpleNavigationService { private readonly IServiceProvider serviceProvider; public SimpleNavigationService(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } public async Task ShowAsync<T>(object parameter = null) where T : Window { var window = serviceProvider.GetRequiredService<T>(); if (window is IActivable activableWindow) { await activableWindow.ActivateAsync(parameter); } window.Show(); } public async Task<bool?> ShowDialogAsync<T>(object parameter = null) where T : Window { var window = serviceProvider.GetRequiredService<T>(); if (window is IActivable activableWindow) { await activableWindow.ActivateAsync(parameter); } return window.ShowDialog(); } }

First of all, at lines 1-4 we define an IActivable interface to mark Windows that support “activation”, i.e. the ability to receive parameters upon loading. Then, the SimpleNavigationService class encapsulates all the logic we need:

it provides methods to open a window, getting it from the ServiceProvider (lines 15-36), so every dependency is automatically resolved and passed to its constructor;

if the window implements the IActivable interface, it calls the ActivateAsync method before actually showing it.

Now we just need to properly register the SimpleNavigationService and all the windows of our application in the ConfigureServices methods we defined in the previous post:

private void ConfigureServices(IServiceCollection services) { // ... // Add SimpleNavigationService for the application. services.AddScoped<SimpleNavigationService>(); // Register all the windows of the applications. services.AddTransient<MainWindow>(); services.AddTransient<DetailWindow>(); }

At the end of the OnStartup method, we can use the new service to show the MainWindow:

protected override void OnStartup(StartupEventArgs e) { // .... var navigationService = ServiceProvider .GetRequiredService<SimpleNavigationService>(); var task = navigationService.ShowAsync<MainWindow>(); }

Now let’s see how to use it. Let’s suppose to have a window with a TextBox and a Button:

<Window ...> <Grid> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="Parameter:" /> <TextBox x:Name="ParameterTextBox" Width="300" /> <Button x:Name="OpenDetailWindowButton" Click="OpenDetailWindowButton_Click" Content="Go to Detail Window" /> </StackPanel> </Grid> </Window>

We want that, pressing the Button at lines 8-11, the DetailWindow will be opened, receiving the value of the TextBox. So, the code-behind in MainWindow.xaml.cs is the following:

public partial class MainWindow : Window { private readonly SimpleNavigationService navigationService; public MainWindow(SimpleNavigationService navigationService) { // ... this.navigationService = navigationService; } private async void OpenDetailWindowButton_Click(object sender, RoutedEventArgs e) { var result = await navigationService .ShowDialogAsync<DetailWindow>(ParameterTextBox.Text); } }

We pass the SimpleNavigationService in the MainWindow constructor through Dependency Injection, then in the button event handler we call the ShowDialogAsync method specifying the type of the Window to open and the parameter to initialize it. To be able to handle this, the DetailWindow code-behind should be something like that:

public partial class DetailWindow : Window, IActivable { public DetailWindow() { // ... } public Task ActivateAsync(object parameter) { ParameterTextBox.Text = parameter?.ToString(); return Task.CompletedTask; } }

As DetailWindow implements the IActivable interface (line 1), SimpleNavigationService will ensure that the ActivateAsync method is called before showing the UI. In this sample, we just set the parameter in a TextBox (line 10). Of course, also the DetailWindow class could receive dependencies in its constructor, if necessary.

You can download the sample app using the link below: