Search code examples
c#.netavaloniauiavaloniacommunity-toolkit-mvvm

Implementing Navigation in Avalonia with MVVM Community Toolkit


I'm trying to implement a simple navigation which is just a MainWindow with a ContentPresenter on top of it. Most of the code is autogenerated. I only added a ContentPresenter, bound its content to a CurrentPage and created a view and a viewmodel for a home page

MainWindow.axaml All of the code is autogenerated except the ContentPresenter

<Window xmlns="https://github.com/avaloniaui"
        ...>

    <Design.DataContext>
        <!-- This only sets the DataContext for the previewer in an IDE,
             to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
        <vm:MainWindowViewModel />
    </Design.DataContext>

        <ContentPresenter Content="{Binding CurrentPage}" />
</Window>

MainWindowViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;

namespace AvaloniaApplication5.ViewModels
{
    public partial class MainWindowViewModel : ViewModelBase
    {
        [ObservableProperty]
        private ViewModelBase _currentPage = new HomeViewModel();
    }
}

HomeView.axaml

A blank UserControl file that should work as a page that ContentPresenter navigates to

<UserControl xmlns="https://github.com/avaloniaui"
             ...>
    Welcome to Avalonia!
</UserControl>

HomeViewModel.cs

namespace AvaloniaApplication5.ViewModels
{
    public class HomeViewModel : ViewModelBase
    {
    }
}

The result of this code should be a display of HomeView.axaml in ContentPresenter but in reality it can't find HomeView.axaml

There's also a ViewLocator that resolves the view that corresponds to a specific ViewModel. This file is entirely autogenerated

ViewLocator.cs

using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AvaloniaApplication5.ViewModels;
using System;

namespace AvaloniaApplication5
{
    public class ViewLocator : IDataTemplate
    {

        public Control? Build(object? data)
        {
            if (data is null)
                return null;

            var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
            var type = Type.GetType(name);

            if (type != null)
            {
                var control = (Control)Activator.CreateInstance(type)!;
                control.DataContext = data;
                return control;
            }

            return new TextBlock { Text = "Not Found: " + name };
        }

        public bool Match(object? data)
        {
            return data is ViewModelBase;
        }
    }
}

It seems like the ViewLocator does indeed work as intended and ContentPresenter wants to display HomeView but it can't find it.

I tried specifying Data Context for HomeView but it didn't do anything

HomeView.axaml.cs

public HomeView()
{
    InitializeComponent();
    DataContext = new HomeViewModel();
}


Solution

  • It's open bug in creating user control template Namespace is missing Views ... the ViewLocator is depended on naming convention

    solution

    correct the namespace

    HomeView.axaml.cs

    using Avalonia.Controls;
    
    namespace AvaloniaApplication5.Views; // <<< make sure that name space Has Views
    
    public partial class HomeView : UserControl
    {
        public HomeView()
        {
            InitializeComponent();
        }
    }
    

    HomeView.axaml

    <UserControl 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"
                 mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                 x:Class="AvaloniaApplication5.Views.HomeView"> 
                 <!--namespace hase Views  ....^^^^^... -->
      Welcome to Avalonia!
    </UserControl>