AvaloniaUI / avalonia-docs

https://docs.avaloniaui.net/docs/welcome
60 stars 190 forks source link

Previewer Unable to find public constructor for type [view model] #466

Closed SimonORorke closed 4 months ago

SimonORorke commented 4 months ago

Describe the bug

In the application I'm developing, the previewer fails to show a preview for any of the views in either Visual Studio 2022 or JetBrains Rider. The error message is "Unable to find public constructor for type [view model]". In Visual Studio, this is a System.Xaml.XamlException. In Rider, where I have to look in idea.log to see the diagnostics, this is a XamlX.XamlLoadException. Here's an example from Rider, where a more fulsome trace is provided:

XamlX.XamlLoadException: Unable to find public constructor for type FalconProgrammer.ViewModel:FalconProgrammer.ViewModel.MidiForMacrosViewModel() Line 12, position 10.
   at XamlX.Transform.Transformers.ConstructableObjectTransformer.Transform(AstTransformationContext context, IXamlAstNode node)
   at XamlX.Transform.AstTransformationContext.Visitor.Visit(IXamlAstNode node)
   at XamlX.Ast.XamlAstNode.Visit(IXamlAstVisitor visitor)
   at XamlX.Ast.XamlAstNode.VisitList[T](IList`1 list, IXamlAstVisitor visitor)
   at XamlX.Ast.XamlPropertyAssignmentNode.VisitChildren(IXamlAstVisitor visitor)
   at XamlX.Ast.XamlAstNode.Visit(IXamlAstVisitor visitor)
   at XamlX.Ast.XamlAstNode.VisitList[T](IList`1 list, IXamlAstVisitor visitor)
   at XamlX.Ast.XamlAstConstructableObjectNode.VisitChildren(IXamlAstVisitor visitor)
   at XamlX.Ast.XamlAstNode.Visit(IXamlAstVisitor visitor)
   at XamlX.Transform.AstTransformationContext.Visit(IXamlAstNode root, IXamlAstTransformer transformer)
   at XamlX.Compiler.XamlCompiler`2.Transform(XamlDocument doc, Boolean strict)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadGroupSreCore(IReadOnlyCollection`1 documents, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadSre(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Designer.HostApp.DesignXamlLoader.Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.DesignerSupport.DesignWindowLoader.LoadDesignerWindow(String xaml, String assemblyPath, String xamlFileProjectPath, Double renderScaling)
   at Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.<>c__DisplayClass18_0.<OnTransportMessage>b__0()

To Reproduce

In Visual Studio or Rider with the required Avalonia extension/plugin installed and making sure preview is enabled, open any view XAML in my open source project https://github.com/SimonORorke/FalconProgrammer.

Expected behavior

A preview should be shown.

Avalonia version

11.0

OS

Windows

Additional context

All the view models only have constructors with parameters.

maxkatz6 commented 4 months ago

But MidiForMacrosViewModel doesn't have a public constructor, while you try to use it as a parameterless DataContext: https://github.com/SimonORorke/FalconProgrammer/blob/main/FalconProgrammer/Views/MidiForMacrosView.axaml#L12

maxkatz6 commented 4 months ago

If you want to resolve previewing data context, you should define this context in C# code where you can access your DI container.

SimonORorke commented 4 months ago

Thanks, @maxkatz6! I got it working. For anyone who encounters the same problem, here is an example. As I mentioned, the public view model constructors in my application all have parameters, like this:

public MidiForMacrosViewModel(IDialogService dialogService,
  IDispatcherService dispatcherService) : base(dialogService, dispatcherService) { }

So I had to define Design.DataContext in code behind instead of in XAML, like this:

public MidiForMacrosView() {
  InitializeComponent();
  // This only sets the DataContext for the previewer in the IDE.
  Design.SetDataContext(this,
    new MidiForMacrosViewModel(new DialogService(), new DispatcherService()));
}

I expect view model constructors with parameters are common, as my understanding is that it is part of the usual way dependency injection works. That is the purpose of the view model constructors with parameters in my project. But I could not find any documentation that covers using the previewer in such a scenario. I would expect to find it in the How To Use Live Preview page of Avalonia's documentation, but I don't see anything there.

Have I missed something in the documentation? If not, I'd be happy to have a go at improving the How To Use Live Preview page and submitting a pull request to the documentation project.

timunie commented 4 months ago

@SimonORorke

a PR to the docs may be a good idea, could also be cross-linked to this one: https://docs.avaloniaui.net/docs/guides/implementation-guides/how-to-implement-dependency-injection

SimonORorke commented 4 months ago

@timunie My pull request commit incorporates some of the points you make, though there were a couple I was not sure about and did not mention. If applicable, please let me know how I may improve the update.