Why is my UserControl not being loaded in my MainWindow?

185 Views Asked by At

I am trying to set up my application to show a different view depending on what the name of the device is. Unfortunately, I can't seem to get the View to load, though the ViewModel is getting called. I'm not entirely sure what I'm missing.

Currently, when the application loads, the only thing shows up is the MainWindow, but the View is not being loaded inside of it.

How can I get a View to load?

Here is my MainWindow.axaml:

<Window
    xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:TAStagingApp.ViewModels.Implementations;assembly=TAStagingApp.ViewModels"
    mc:Ignorable="d"
    d:DesignWidth="800"
    d:DesignHeight="450"
    x:Class="TAStagingApp.Views.MainWindow"
    x:DataType="vm:MainWindowViewModel"
    Background="{StaticResource TABackgroundColor}"
    Icon="/Assets/tastaging.png"
    WindowStartupLocation="CenterScreen"
    Title="TA Staging">

    <Design.DataContext>
        <vm:MainWindowViewModel />
    </Design.DataContext>
    <Grid>
        <ContentPresenter Name="StagingContent"/>
    </Grid>

</Window>

And the MainWindow.axaml.cs

namespace TAStagingApp.Views;
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    public void SetContent(Control control)
    {
        var mainContent = this.FindControl<ContentPresenter>("StagingContent");
        mainContent!.Content = control;
    }
}

App.axaml.cs

using AvaloniaApplication = Avalonia.Application;

namespace TAStagingApp;
public partial class App : AvaloniaApplication
{
    public override void Initialize()
    {
        AvaloniaXamlLoader.Load(this);
    }

    public override void OnFrameworkInitializationCompleted()
    {
        var viewModelLoader = new ViewModelLoader(new MainWindow());
        ServiceLocator.Initialize(viewModelLoader);

        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            DataContext = GetRequiredService<IMainWindowViewModel>();
            desktop.MainWindow = new MainWindow
            {
                DataContext = DataContext,
            };

            viewModelLoader.LoadContentViewBasedOnComputerName();
        }

        base.OnFrameworkInitializationCompleted();
    }

    private static T GetRequiredService<T>() => Locator.Current.GetRequiredService<T>();
}

ViewModelLoader.cs

namespace TAStagingApp.DependencyInjection.Loaders;
public sealed class ViewModelLoader(MainWindow mainWindow) : IViewModelLoader
{
    private readonly MainWindow _mainWindow = mainWindow;

    public void LoadContentViewBasedOnComputerName()
    {
        var serviceProvider = DeviceTypeHelper.GetDeviceTypeServiceProvider();
        var deviceType = DeviceTypeHelper.GetDeviceType(serviceProvider);

        Control contentView = deviceType.Value.ToString() switch
        {
            var device when device.Equals(DeviceType.Server.ToString()) => new ServerConfigureView(),
            var device when device.Equals(DeviceType.Terminal.ToString()) => new TerminalConfigureView(),
            var device when device.Equals(DeviceType.Kitchen.ToString()) => new TerminalConfigureView(),
            _ => new AdminView()
        };

        _mainWindow.SetContent(contentView);
    }
}

MainWindowViewModel.cs

namespace TAStagingApp.ViewModels.Implementations;

public class MainWindowViewModel : ViewModelBase, IMainWindowViewModel
{
    public MainWindowViewModel(IActivateViewModel viewModel)
    {
        if (viewModel.GetType() == typeof(TerminalConfigureViewModel))
        {
            TerminalConfigureViewModel = viewModel;
        }
        else if (viewModel.GetType() == typeof(ServerConfigureViewModel))
        {
            ServerConfigureViewModel = viewModel;
        }
        else
        {
            AdminViewModel = viewModel;
        }

        Activate(viewModel);
    }

    public MainWindowViewModel()
    {
    }

    public IActivateViewModel AdminViewModel { get; } = default!;

    public IActivateViewModel TerminalConfigureViewModel { get; } = default!;

    public IActivateViewModel ServerConfigureViewModel { get; } = default!;

    private static void Activate(IActivateViewModel viewModel)
        => viewModel.Activate();
}

I've been looking at this for two days now and can't figure out what I'm missing.

1

There are 1 best solutions below

4
On BEST ANSWER

The problem is your ViewModelLoader is working with an instance of MainWindow which is different from the actual

public override void OnFrameworkInitializationCompleted()
{
    var viewModelLoader = new ViewModelLoader(new MainWindow()); // <<<< this window 
    ServiceLocator.Initialize(viewModelLoader);

    if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
    {
        DataContext = GetRequiredService<IMainWindowViewModel>();
        desktop.MainWindow = new MainWindow // <<< diffrent from this window
        {
            DataContext = DataContext,
        };

        viewModelLoader.LoadContentViewBasedOnComputerName();
    }

    base.OnFrameworkInitializationCompleted();
}

you can fix it by instantiating ViewModelLoader with the application Main window

public override void OnFrameworkInitializationCompleted()
{
    if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
    {
        DataContext = GetRequiredService<IMainWindowViewModel>();
        desktop.MainWindow = new MainWindow
        {
            DataContext = DataContext,
        };

        // <<< instantiate ViewModelLoader with the application MainWindow
        var viewModelLoader = new ViewModelLoader(desktop.MainWindow);
        ServiceLocator.Initialize(viewModelLoader);

        viewModelLoader.LoadContentViewBasedOnComputerName();
    }

    base.OnFrameworkInitializationCompleted();
}

Side Advice

Actually, you should redesign your architecture because you get architecture dead look!

If I were you, I will use data templates to make my life easy!

 <Window.DataTemplates>
   <DataTemplate DataType="{x:Type vm:ServerConfigureViewModel}">
     <view:ServerConfigureView/>      
   </DataTemplate>

   <DataTemplate DataType="{x:Type vm:TerminalConfigureViewModel}">
     <view:TerminalConfigureView/>
   </DataTemplate>

   <DataTemplate DataType="{x:Type vm:AdminViewModel}">
     <view:AdminView/>
   </DataTemplate>
 </Window.DataTemplates>

And put in your MainViewModle Property Called CurrentViewModel or ActivatedViewModel or Whatever of type ViewModelBase

public class MainWindowViewModel : ViewModelBase
{
    private ViewModelBase _currentViewModel;

    public ViewModelBase CurrentViewModdel
    {
        get => _currentViewModel;
        set => SetProperty(ref _currentViewModel, value);
    }

}

All of ServerConfigureViewModel, TerminalConfigureViewModel and AdminViewModel should inherit from ViewModelBase and can be signed to CurrentViewModdel

and bind to it in main Window

<Grid>
  <ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>

now if you change CurrentViewModdel in Main Window View will change without all this code of ViewModleLocator and this staff