Giter Site home page Giter Site logo

thomasclaudiushuber / mvvmgen Goto Github PK

View Code? Open in Web Editor NEW
245.0 14.0 21.0 764 KB

MvvmGen is a lightweight MVVM library for XAML applications. It generates your ViewModels on-the-fly for you via a Roslyn-based C# Source Generator.

License: MIT License

C# 100.00%
mvvm xaml wpf winui maui source-generator csharp uno xamarin-forms uno-platform

mvvmgen's Introduction

⚡ MvvmGen

Build MvvmGen NuGet MvvmGen NuGet MvvmGen

Your Friend Who Writes the Boilerplate for You

Hey there, welcome to the MvvmGen repository. MvvmGen is a lightweight and modern MVVM library (.NET Standard 2.0) built with C# Source Generators that helps you to apply the popular Model-View-ViewModel-pattern (MVVM) in your XAML applications that you build with WPF, WinUI, Uno Platform, Avalonia, Xamarin Forms, or .NET MAUI.

MvvmGen is licensed under the MIT license.

Get Started

Quick intro

In this quick intro, you'll learn that creating a ViewModel is a lot of fun with MvvmGen! 🔥

Installing the MvvmGen NuGet Package

Reference the NuGet package MvvmGen in your .NET application, and then you're ready to go:

Install-Package MvvmGen

MvvmGen will register itself as a C# source generator in your project, and it will be your friend who writes the boilerplate for you.

Generating a ViewModel class

To generate a ViewModel class, you create a new class, you mark it as partial, and you put MvvmGen's ViewModel attribute on the class:

using MvvmGen;

namespace MyWpfApp.ViewModel
{
  [ViewModel]
  public partial class EmployeeViewModel
  {
  }
}

The ViewModel attribute tells MvvmGen to generate another partial EmployeeViewModel class. Right now, it will be a class that looks like this:

using MvvmGen.Commands;
using MvvmGen.Events;
using MvvmGen.ViewModels;

namespace MyWpfApp.ViewModel
{
    partial class EmployeeViewModel : ViewModelBase
    {
        public EmployeeViewModel()
        {
            this.OnInitialize();
        }

        partial void OnInitialize();
    }
}

You can see that generated class in Visual Studio under Dependencies->Analyzers: Generated class

Beside the ViewModel attribute, you find many other attributes in the MvvmGen namespace that you can use to decorate your ViewModel class. These attributes allow you to build a full ViewModel like this:

using MvvmGen;
using MvvmGen.Events;

namespace MyWpfApp.ViewModel
{
  public record EmployeeSavedEvent(string FirstName, string LastName);

  [Inject(typeof(IEventAggregator))]
  [ViewModel]
  public partial class EmployeeViewModel
  {
    [Property] private string _firstName;
    [Property] private string _lastName;

    [Command(CanExecuteMethod = nameof(CanSave))]
    private void Save()
    {
      EventAggregator.Publish(new EmployeeSavedEvent(FirstName, LastName));
    }

    [CommandInvalidate(nameof(FirstName))]
    private bool CanSave()
    {
      return !string.IsNullOrEmpty(FirstName);
    }
  }
}

For this ViewModel, MvvmGen will generate the following partial class definition for you

using MvvmGen.Commands;
using MvvmGen.Events;
using MvvmGen.ViewModels;

namespace MyWpfApp.ViewModel
{
  partial class EmployeeViewModel : ViewModelBase
  {
    private IDelegateCommand? _saveCommand;

    public EmployeeViewModel(MvvmGen.Events.IEventAggregator eventAggregator)
    {
      this.EventAggregator = eventAggregator;
      this.OnInitialize();
    }

    partial void OnInitialize();

    public IDelegateCommand SaveCommand => _saveCommand ??= new DelegateCommand(_ => Save(), _ => CanSave());

    public string FirstName
    {
      get => _firstName;
      set
      {
        if (_firstName != value)
        {
          _firstName = value;
          OnPropertyChanged("FirstName");
        }
      }
    }

    public string LastName
    {
      get => _lastName;
      set
      {
        if (_lastName != value)
        {
          _lastName = value;
          OnPropertyChanged("LastName");
        }
      }
    }

    protected MvvmGen.Events.IEventAggregator EventAggregator { get; private set; }
    
    protected override void InvalidateCommands(string? propertyName)
    {
      base.InvalidateCommands(propertyName);
      if(propertyName == "FirstName")
      {
          SaveCommand.RaiseCanExecuteChanged();
      }
    }
  }
}

To learn all the details, go to the documentation in this repo.

mvvmgen's People

Contributors

laurentkempe avatar seidchr avatar simoncropp avatar thomasclaudiushuber avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mvvmgen's Issues

Custom ICommand implementation

Hi,
I recently discoverd this very well thought tool, which makes life so much easier for the MVVM players.

I wonder if there is a way to have a custom ICommand implementation instead of the default DelegateCommand, let's say a "MyCommand", and have the generator use that instead of the default one.

All the best,
Alex

Switch ViewModelGenerator from ISourceGenerator to IIncrementalGenerator

Since .NET 6.0, there's a new interface for Source Generators: IIncrementalGenerator.

It's the recommended way for new projects and generators shared as NuGet packages. With IIncrementalGenerator the code generation does not run on every code change. Instead, there's a filter pipeline to ensure high performance.

Docs in Roslyn repo: https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md

ISourceGenerator vs IIncrementalGenerator: Great thoughts by @Chiser99: https://twitter.com/thomasclaudiush/status/1571429921439862785

This issue is here to switch the code generation of MvvmGen from ISourceGenerator to IIncrementalGenerator.

Release 1.2.1

Hey MvvmGen friends,

MvvmGen was released in version 1.2.1!

Beside bug fixes, the brand new feature is the [ViewModelGenerateInterface] attribute. You find the documentation for this new feature here: https://github.com/thomasclaudiushuber/mvvmgen/blob/main/docs/07_generate_a_viewModel_interface.md).

Thank you for your help and support ❤️, I hope you're enjoying MvvmGen as much as I do. ✨

Thomas

Release notes

Version 1.2.1

  • Ensure that [ViewModelGenerateInterface] also generates command properties (Issue #64)

Version 1.2.0

  • New [ViewModelGenerateInterface] attribute (Issue #48 - Thank You @Daimonion1980)
    • Generate an interface for a ViewModel. This supports more unit testing scenarios
    • If [ViewModelGenerateInterface] is set, [ViewModelGenerateFactory] will return the interface type instead of the ViewModel type.
    • New ReturnType property on [ViewModelGenerateFactory] attribute allows you to explicitly define a return type of the factory.
  • Source generator implements now the newer IIncrementalGenerator interface (Issue #45)
  • The model used by the source generator implements Equals to support caching between generation steps. This makes the generator more performant, which is especially noticable in larger solutions. (Issue #51)
  • Bug fix for [ViewModelGenerateFactory] attribute. When injecting an IEventAggregator into the ViewModel, the factory now also has the correct constructor parameters. (Issue #44 - Thanks again @Daimonion1980)
  • Minor code optimizations

Inject logger in WPF

When I try to inject a logger into a ViewModel an exception is being thrown

[Inject(typeof(Logger<MainViewModel>), PropertyAccessModifier=AccessModifier.Private, PropertyName = "logger")]
[Inject(typeof(IEventAggregator))]
[ViewModel]
public partial class MainViewModel
{
	[Property] bool _isBusy;
	[Property] String _busyMessage;	

Generated Code
public MainViewModel(MvvmGen.Events.IEventAggregator eventAggregator, Microsoft.Extensions.Logging.Logger<FamilyGenerations.ViewModels.MainViewModel> logger)
{
this.EventAggregator = eventAggregator;
this.logger = logger;
this.OnInitialize();
}

Exception:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Logging.Logger1[FamilyGenerations.ViewModels.MainViewModel]' while attempting to activate 'FamilyGenerations.ViewModels.MainViewModel'. at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType) at System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance[T](IServiceProvider provider, Object[] parameters)
at FamilyGenerations.App.OnStartup(StartupEventArgs e) in C:\TFSSource\Client Applicaitons\FamilyGenerations\FamilyGenerations\App.xaml.cs:line 58
at System.Windows.Application.<.ctor>b__1_0(Object unused)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)

This works correctly:
public partial class MainWindow : Window
{
private readonly ILogger log;
private readonly MainViewModel _model;

	public MainWindow(MainViewModel model, ILogger<MainWindow> log) //, MainViewModel model
	{
		InitializeComponent();
		this.log = log;
		this._model = model;
		this.DataContext = _model;
		log.LogInformation("Main Window loaded, Staring Application.");
	}
}

Thanks
Bob

ViewModel Inheritance not working

Hi,

the constructor is not generated correctly if i inheritade from a ViewModel with parameter in the constructor. The inherited class is not calling the base class constructor with its parameter.

[ViewModel] [Inject(typeof(IPermissionManager), PropertyName = "PermissionManager", PropertyAccessModifier = AccessModifier.Public)] public partial class ViewModelWithSuggestions {...}

Inheritating from ViewModelWithSuggestions:

[ViewModel] public partial class PhotoareaPropertyGridViewModel : ViewModelWithSuggestions {...}

Will lead to this generated code:

partial class PhotoareaPropertyGridViewModel { public PhotoareaPropertyGridViewModel() { this.InitializeCommands(); this.OnInitialize(); } }

Not compiling with error

image

ViewModel constructor cannot be overloaded

Hello,

Today it is not possible for your view model to have several constructor signature.
If you try to add to add a custom constructor in your view model source file, you cannot initialize commands:

  • You cannot directly call InitializeCommands() because the method is not marked as partial.
  • You cannot call the generated constructor because it is not marked as partial.

I wish for at least one of them to be partial so I can call it in my code.

The .Net SDK 6.0.302 fail compiling MvvmGen projects using WPF

Describe the bug

The .Net SDK 6.0.302 fail compiling MvvmGen projects using WPF. (Projects which compile correctly if you use .Net SDK 6.0.300)

I have open a bug report on the SDK side too because I am not sure if it is a MvvmGen issue or SDK issue.

To Reproduce

This ConsoleApp8.zip can reproduce the issue

  • Install .Net SDK 6.0.300 (you need to uninstall .Net SDK 6.0.302 to install the 6.0.300)
  • Install .Net SDK 6.0.302
  • Check both SDK are installed using dotnet --list-sdks
  • Unzip ConsoleApp8.zip (attached to this issue)
  • Run dotnet build ConsoleApp8.sln
  • Notice compilation fail
  • Modify the global.json file and change the value from 6.0.302 to 6.0.300
  • Run dotnet build ConsoleApp8.sln
  • Notice the compilation is successful

Further technical details

  • I can also reproduce the problem by executing the build through Visual Studio 2022 GUI
  • The problem disappear if you remove the xmlns:local="clr-namespace:MyNamespace" on MyApp.xaml
  • The compilation error looks like:
ConsoleApp8\MvvmGen.SourceGenerators\MvvmGen.ViewModelGenerator\MyNamespace.MyViewModel.g.cs(19,22,19,34): error CS0756: A partial method may not have multiple defining declarations
ConsoleApp8\MvvmGen.SourceGenerators\MvvmGen.ViewModelGenerator\MyNamespace.MyViewModel.g.cs(14,16,14,27): error CS0111: Type 'MyViewModel' already defines a member called 'MyViewModel' with the same parameter types
ConsoleApp8\MvvmGen.SourceGenerators\MvvmGen.ViewModelGenerator\MyNamespace.MyViewModel.g.cs(19,22,19,34): error CS0111: Type 'MyViewModel' already defines a member called 'OnInitialize' with the same parameter types

Can't inject ILogger<T>

Hi Thomas,

great package, I'm using it already in two projects.

I like to inject an ILogger in my view model, but that creates code that does not compile.

E.g.

[Inject(typeof(ILogger<SettingsPageViewModel>))]

Is that a known issue? Or should that work?

Regards
Martin

Prepare release 1.2.1

Includes the new feature to generate command properties in the generated interface

Support Explicit and Implicit Array Argument in PropertyInvalidateAttribute

The PropertyInvalidateAttribute does not support explicit array creation for more parameters.

This attribute here works fine:

[PropertyInvalidate(nameof(FirstName), nameof(LastName))]

The following attributes should do the same with explicit and implicit array creation, but they don't work, only the FirstName is invalidated in this case:

[PropertyInvalidate(nameof(FirstName), new string[]{nameof(LastName)})]
[PropertyInvalidate(nameof(FirstName), new []{nameof(LastName)})]

This is related to issue #21 and the CommandInvalidateAttribute

Input Validation with INotifyDataErrorInfo

MvvmGen should support an easy way to implement input validation with the INotifyDataErrorInfo interface.

  • A ValidationViewModelBase class is something that could make sense.
  • A partial method that users could implement to return validation errors would also be great.
  • Also check if there is an easy way to integrate this with common validation patterns, like Data Annotations and FluentValidation

Note: This issue might make sense when WinUI gets its input validation done: microsoft/microsoft-ui-xaml#179

Invalidate Readonly Model Property in ViewModel

This could be me not understanding how to use the library. Anyway, here is the simplest example of what I have.

  public class MyModel
  {
    private const string path = @"CppLib.dll";
    private readonly IntPtr _class;

    [DllImport(path, CallingConvention = CallingConvention.Cdecl)]
    private static extern uint GetNumber(IntPtr _class);

    public uint Number { get { return GetNumber(_class); } } //"Number" is derived from "Attribute"

    [DllImport(path, CallingConvention = CallingConvention.Cdecl)]
    private static extern uint AlterAttribute(IntPtr _class, uint attribute); //This will change "Number"

    private uint attribute;
    public int Attribute
    {
      get { return (int)attribute; }
      set
      {
        if (adapter != value)
        {
          this.AlterAttribute(attribute);
        }
      }
    }

    private void AlterAttribute(uint attribute)
    {
      AlterAttribute(_class, attribute)
    }
  }

  [ViewModel(typeof(MyModel))]
  public partial class MyViewModel
  {
    partial void OnInitialize()
    {
      Model = new MyModel();
    }
  }

I'm looking for OnPropertyChanged("Number"); in the public int Attribute setter of the MyViewModel boiler plate code.

I tried placing [PropertyInvalidate(nameof(Attribute))] above the Number property in the model but that didn't do anything.

Attribute ViewModelGenerateFactory ignores IEventAggregator ConstructorArgument

When using attribute ViewModelGenerateFactory together with IEventSubscriber mvvmgen does not add the IEventAggregator constructor argument to the factory class.

Sample ViewModel code

using SampleApp.Events;
using MvvmGen;
using MvvmGen.Events;
using System;

namespace SampleApp.ViewModel
{
    [ViewModelGenerateFactory]
    [ViewModel]
    public partial class SampleViewModel : IEventSubscriber<SampleEvent>
    {
        public void OnEvent(SampleEvent eventData)
        {
            throw new NotImplementedException();
        }
    }
}

The generated code looks as follows:

//   Generator version: 1.1.5
// </auto-generated>
using MvvmGen.Commands;
using MvvmGen.Events;
using MvvmGen.ViewModels;

namespace SampleApp.ViewModel
{
    partial class SampleViewModel : global::MvvmGen.ViewModels.ViewModelBase
    {
        public SampleViewModel(MvvmGen.Events.IEventAggregator eventAggregator)
        {
            eventAggregator.RegisterSubscriber(this);
            this.OnInitialize();
        }

        partial void OnInitialize();
    }

    public interface ISampleViewModelFactory : IViewModelFactory<SampleViewModel> { }

    public class SampleViewModelFactory : ISampleViewModelFactory
    {
        public SampleViewModelFactory()
        {
        }

        public SampleViewModel Create() => new SampleViewModel();
    }
}

As you can see the factory tries to call an empty constructor, whereas the constructor needs the IEventAggregator to register the event he inherits with IEventSubscriber.

generating Viewmodel from derived model

class A
{
string id{get; set;}
string Name{get; set;}
}

class B : A
{
string Email {get; set; }
}

the generated ViewModel from B must include props from A but it doesn't.

Feature Request: ViewModelFactory Create() method should be able to return interface to ViewModel it creates

In my application I'm using an ObservableCollection inside a ViewModel class to host a various amount of another ViewModel instances like it is implemented in EmployeeManager in MainViewModel.cs. To explain the purpose of this request I will use the names from EmployeeManager.

Mvvmgen generates a factory to EmployeeViewModel, the EmployeeViewModelFactory, and its method Create() returns a new instance of EmployeeViewModel via this attributes:

[ViewModelGenerateFactory]
[ViewModel(typeof(Employee))]
public partial class EmployeeViewModel
{...}

The Factory is used to create a variable amount of EmployeeViewModel instances in the OnEvent() Method of MainViewModel.cs

public async void OnEvent(EmployeeNavigationSelectedEvent eventData)
{
	var employeeViewModel = EmployeeViewModels.SingleOrDefault(x => x.Id == eventData.EmployeeId);
	if (employeeViewModel is null)
	{
		employeeViewModel = EmployeeViewModelFactory.Create();
		employeeViewModel.Load(eventData.EmployeeId);
		EmployeeViewModels.Add(employeeViewModel);
		await Task.Delay(1); // Little hack for WinUI TabView. If this is not here, WinUI won't select the tab,
							 // as next statement is setting the SelectedEmployee property
	}

	SelectedEmployee = employeeViewModel;
}

When Unit testing the OnEvent method of MainViewModel.cs we are able to mock the factory class because it is implemented with an interface IEmployeeViewModelFactory.
(I'm using moq as mocking framework in my application)

But to get this mock setup correctly we have to define the behaviour of the Create() method which is called inside the OnEvent() method.
But as defined in mvvmgen the Create() method returns a instance of a concrete class (EmployeeViewModel in this case) and we are not able to mock this class because it has no interface.

So, to test the OnEvent() Method correctly it would be better that mvvmgen is able to generate a factory which returns an interface to the ViewModel (IEmployeeViewModel) it shall generate. With this we would be able to mock the following call for the Load method and we could test the specification of the OnEvent() method.

var moqEmployeeViewModelFactory = new Mock<IEmployeeViewModelFactory>();
var moqEmployeeViewModel = new Mock<IEmployeeViewModel>();	//this will not work, because out of the box, this interface does not exist, but w could create it manually
moqEmployeeViewModel.Setup(m => m.Load(It.IsAny())); //Setup for Call in onEvent of MainViewModel.cs
moqEmployeeViewModelFactory.Setup(m => m.Create()).Returns(moqEmployeeViewModel.Object); //this will also not work, because Create() returns instance of EmployeeViewModel and not IEmployeeViewModel

So, when it would be possible to say mvvmgen that it should create an Interface to this ViewModel (maybe through [ViewModelGenerateInterface]) and when it would be possible to define the return value of the factory (maybe through [ViewModelGenerateFactory(typeof(IEmployeeViewModel)) the unit test will be possible and the Viewmodel connection (I'm looking at the ObservableCollection in MainViewModel.cs) would be more modular.

How to exclude IEnumerable Model properties from generation

Hi,

I have a Client Model, which has a Name, StreetAddress etc simple properties, but it also has an
IEnumerable<Order> Orders {get; set;}
property.

When I generate my ClientViewModel by decorating it with [ViewModel(typeof(Client))], I get all the properties of the Client model, including the Orders. However, I would like instead to add by hand an IEnumerable<OrderViewModel> Orders.

So the question is, what is the best way to tell the generator to include only the simple (scalar) properties in the genaration?

Best regards,
Alex

Transient ViewModels with IEventSubscriber

I'm not sure if I'm doing it wrong: I have some ViewModels that implement IEventSubscribe. If the ViewModel gets added to the Dependency Container using "AddTransient" a new ViewModel-object gets created whenever requested. I think that should be fine - but the implementation of OnEvent() gets called for every generated ViewModel during runtime.
so I would have to use AddSingleton for my ViewModels - which might lead to other errors if the ViewModels are not correctly initialized every time again.

Is my understanding here wring or is this an error in MVVMGen? Should I explicitly Unsubscribe my ViewModel after use? That would mean injecting the EventAggregator into every viewmodel and unsubscribe in the destructor or something like this.

Any ideas or hints on this? Thank you!

Attributes often fail

This library often leads to attribute invalidation. You need to manually delete the obj directory and regenerate it to recover

Suppress nullable warnings

As variables and commands get initialized not in constructor but in seperate methods code analyzer does net recognize it and throw always CS8618 warnings. Partly workaround is adding a MemberNotNull attribute on the OnInitialize method. This is not nice but works at least for all Properties. But it is not possible to do this for the commands as they are in the generated file part. I tried a lot to disable these warning but nothing works, pargam once, SuppressMessage attribute or rules in .editorconfig file for [*.g.cs] files. Could it be an option to add warnings to suppress in the next version ?

Implement Equals in ViewModelToGenerate class to support caching

An Incremental source generator caches the model and checks if there are any changes with the next run. Only if there are changes, it generates the files again.

Currently, the used model, the ViewModelToGenerate class, does not implement an Equals method. This means that the files for ViewModels are generated on every key press in the code editor, even if there are no changes in the ViewModel, as the key press can happen in any file. By implementing an Equals method we can ensure that files are only generated if the model - a ViewModelToGenerate instance - really changed compared to the previous run. The incremental generator will take care of the caching. All we need to do is to implement a proper equals method.

This will increase performance of the source generator, which is especially important in very large solutions.

To implement an Equals method, the boarder between building the model and code generation needs to be done a bit cleaner as well. After the model, the ViewModelToGenerate instance was created, there should be no need anymore to use the syntax API or semantic model at a later stage. Instead, all the data needed to generate should be in the ViewModelToGenerate instance.

Consider generating Microsoft.Toolkit.Mvvm based implementation

Microsoft.Toolkit.Mvvm is a Windows Community Toolkit project that provides base classes useful when implementing the MVVM pattern. It provides quality implementations of useful patterns used in MVVM applications, especially ObservableObject, RelayCommand and AsyncRelayCommand. Generating code based on that toolkit would mean less code generated, easier integration of advanced mvvm concepts, and concentrating on code generation.

Link to official documentation: https://docs.microsoft.com/en-us/windows/communitytoolkit/mvvm/introduction

Unable to get canexecutemethod change notification from inherited class

[ViewModel]
public abstract partial class TreeModel
{
        [Property]
        public ColItem? _rootNode;

        [Property]
        public ColItem? _selectedNode;
}

[ViewModel]
public partial class LocalTreeModel : TreeModel
{
       [Command(CanExecuteMethod = nameof(CanEditNode))]
        public void EditNode()
        {

        }

        [CommandInvalidate(nameof(RootNode))]
        [CommandInvalidate(nameof(SelectedNode))]
        public bool CanEditNode() => RootNode is not null && SelectedNode is not null && SelectedNode != RootNode;
}

Generating properties from a model which has a Model property fails

Hi Thomas,

I'm a big fan of MVVMGen and use it very often. Most of the time my ViewModels are based on an existing model class.

Today I encountered a showstopper because my model class already contains a Model property, causing a collision with the generated protected property for the Model class.

Possible solutions: the quickest would be to rename the generated internal protected property (e.g. _model instead of Model). Perfect would be to add another parameter to the ViewModelAttribute to set the internal name.

Thanks
Martin

Raise CommandInvalidate when adding or removing elements on collection

Hello Thomas.

Is it possible to raise a CommandInvalidate method when changing the number of elements on an ObservableCollection?

In my code i have an ObservableCollection where i can add or remove elements via command methods.
Now I want to implement a clear button which will become enabled when the ObservableCollection has more than zero elements.

[ViewModel]
[ViewModelGenerateFactory]
public partial class VM_ParameterChooser 
{
	[Property] ObservableCollection<VM_Parameter> selectedParameterList = new ObservableCollection<VM_Parameter>();

	[Command(CanExecuteMethod = nameof(SelectedListCanBeCleared))]
	public void Clear()
	{
		SelectedParameterList.Clear();
	}

	//this method is only called when SelectedParameterList is changed itself
	//but should be called if a element is added or removed from the collection
	[CommandInvalidate(nameof(SelectedParameterList))]	
	public bool SelectedListCanBeCleared() => SelectedParameterList.Count > 0;

	[Command]
	public void AddSelectedParameter(object param)
	{
		var selectedParam = param as VM_Parameter;
		if (selectedParam is null) return;
		SelectedParameterList.Add(selectedParam);
	}

	[Command]
	public void RemoveSelectedParameter(object param)
	{
		var selectedParam = param as VM_Parameter;
		if (selectedParam is null) return;
		SelectedParameterList.Remove(selectedParam);
	}
}

Clean up model creation and code generation

Right now, the ViewModelToGenerate class contains for example the INamedTypeSymbol for the ViewModel class. All that syntax API stuff should disapear from the model, so that there's a very clean boarder between building up the model and using the model to generate code. When building up the model, the syntax API and the semantic model can be used. After that stage, the model should contain all the data needed to generate the code.

This step is recommended before implementing the Equals method in the ViewModelToGenerate class as described in issue #51

Custom getter for properties for lazy loading

Currently with PropertyCallMethodAttribute we can add method calls for property setters but getters will always only use the backing field.

Please add an other attribute for getter to support lazy loading.
Example:

class A
{
    [Property]
    [CustomGetter(LazyLoadTest)]
    private List<string> _test;

    private List<string> LazyLoadTest(List<string> currentValue)
    {
        return currentValue ?? new List<string>(){"testA", "testB"};
    }

    #region Generated

    public List<string> Test
    {
        get => _test = LazyLoadTest();
        set =>
        {
            // No changes
        }
    }

    #endregion
}

This getter implementation is just a sample, I'm not sure this is best one.

Updating property does not reflect bound TextBox in Windows App SDK.

  • MvvmGen 1.1.2
  • Windows App SDK (Experimental) 1.0.0.50489432
  • VisualStudio 2019 16.11.5

I am starting to use MVVMgen. Thank you for sharing great library, I like it very much. Now I am probably doing something wrong and I am not able to figure it out what is happening. I have limited experience on XAML.

A text box is binding to a property in a view model in the sample below. When you press the button and select a folder, the model view updates the property, but text box on the window does not update.

XAML

<StackPanel>
    <TextBox Header="Local Path" Margin="0,0,0,16" Text="{Binding LocalPath, UpdateSourceTrigger=PropertyChanged}"/>
    <Button Content="Choose Folder" Margin="0,0,0,16" Command="{Binding SelectLocalPathCommand}"/>
</StackPanel>

View

namespace MVVM_TextBox
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            DataContext = new ViewModels.MainPageViewModel();
        }
    }
}

Model

namespace MVVM_TextBox.ViewModels
{
    [ViewModel]
    public partial class MainPageViewModel
    {
        [Property] string _localPath = "";

        [Command]
        private async void SelectLocalPath()
        {
            var picker = new FolderPicker();
            picker.FileTypeFilter.Add("*");
            var folder = await picker.PickSingleFolderAsync();
            if (folder != null)
            {
                LocalPath = folder.Path;
            }
        }
    }
}

g.cs file generated by MVVM gen.

namespace MVVM_TextBox.ViewModels
{
    partial class MainPageViewModel : global::MvvmGen.ViewModels.ViewModelBase
    {
        public MainPageViewModel()
        {
            this.InitializeCommands();
            this.OnInitialize();
        }

        partial void OnInitialize();

        private void InitializeCommands()
        {
            SelectLocalPathCommand = new DelegateCommand(_ => SelectLocalPath());
        }

        public DelegateCommand SelectLocalPathCommand { get; private set; }

        public string LocalPath
        {
            get => _localPath;
            set
            {
                if (_localPath != value)
                {
                    _localPath = value;
                    OnPropertyChanged("LocalPath");
                }
            }
        }
    }
}

Complete project file can be found at: https://github.com/hayashida-katsutoshi/MVVM-TextBox

How to use mvvmgen to communicate from viewmodel to view?

Hello

I'm pretty new to C#/MVVM and in special with mvvmgen.

From what I learned so far is that the way to communicate from view to viewmodel is done via command functionality.

When there is the need to show something, for example a messagebox, from the viewmodel the internet suggest this to do this with a delegate functionality.

ViewModel:

public class SampleViewModel : ViewModelBase
{
	public delegate MessageBoxResult MessageBoxDelegateWithoutContent(string title,string text);
	
	public MessageBoxDelegateWithoutContent MBErrorInitialize {get; set;}
	
	private void SomeMethod()
	{
		this.MBErrorInitialize("MBErrorInitialize_Caption", "MBErrorInitialize_Text");
	}
}

View:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
	SampleViewModel? viewmodel = this.DataContext as SampleViewModel;
	if (viewmodel != null)
	{ 
		viewmodel.MBErrorInitialize = (title, text) =>
		{
			return MessageBox.Show(title, text, MessageBoxButton.OK, MessageBoxImage.Error);
		};
	}
}

Not the very best solution because in viewmodel there has to be used view code (MessageBoxResult) but this returntype can be changed to a own one.

I don't see any suggestion in mvvmgen for this path (ViewModel --> View).

Is it a good pattern to communicate from viewmodel to view with delegates?
Does mvvmgen define a support method for this communication?
If not does i make sense to implement one?

Thanks for your work on mvvmgen. I appreciate it a lot!

ViewModelBase must be fully qualified

I've had build issues with the generated code out of the box, because we have a "ViewModelBase" class in the very same namespace.
Please qualify the referenced ViewModelBase class in generated code, and/or make the base-class configurable in the [ViewModel] attribute.

Add #nullable directive to generated source files

When you create a simple ViewModel like this

[ViewModel]
public partial class MainViewModel
{
    [Property] private string? _firstName;
}

Then the C# compiler will output this warning:

Warning CS8669 The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source. RegistrationScreen.Wpf D:\Repos\mvvmgen-samples\RegistrationScreen\RegistrationScreen.Wpf\MvvmGen.SourceGenerators\MvvmGen.ViewModelGenerator\MvvmGenSamples.RegistrationScreen.Wpf.ViewModel.MainViewModel.g.cs 21 Active

That means the #nullable directive should be added to the generated source files.

Implement Diagnostics for Generator

The ViewModelGenerator has some requirements for the input code. For example, a ViewModel class needs to be in a namespace.

If that's not the case, the user does not get an error right now. For this, the ViewModelGenerator should return appropriate diagnostics, so that the user can see what's wrong with their code.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.