reactiveui / ReactiveUI.SourceGenerators

Use source generators to generate objects.
MIT License
31 stars 3 forks source link

Source Generators for specialized ReactiveUI controls (Winforms, WPF, etc) #35

Open Micha-kun opened 3 months ago

Micha-kun commented 3 months ago

I'm working with ReactiveUI in a WinForms app with DevExpress but I'm having a hard time because ReactiveUI winforms controls (ReactiveUserControl, RoutedControlHost, RoutedControlHost , etc) inherits directly from UserControl, and because i'm working with DevExpress, I'm losing DevExpress benefits like Skin customization or better UI integration because they doesn't implement XtraUserControl (UserControl from DevExpress). The only thing I can do is go to ReactiveUI Source Code, copy the source from those controls and change in my code UserControl for XtraUserControl. And the pain is that I need to check every time ReactiveUI has upgraded if the Source Code changed and update manually.

But now in the Source Generators era... Maybe we can make it easy this "specialization". Why not create a SourceGenerators for Winforms (ReactiveUI.SourceGenerators.WinForms) that would generate an specialization by attribute (like [ReactiveUserControlAttribute()] that can get a type parameter as argument implying which base class to inherit, so if no parameter used, standard UserControl class is applied? Could be an assembly attribute, so only a single time is required to write, or an specialization per class if required.

It's doable? I think it can be exportable to any technology that currently ReactiveUI has specialized controls (WPF, etc)

Thank you for your attention

ChrisPulman commented 3 months ago

https://github.com/reactiveui/ReactiveUI.SourceGenerators?tab=readme-ov-file#usage-iviewfor-iviewfornameofviewmodelname Is this kind of functionality what you are hoping for?

GitHub
GitHub - reactiveui/ReactiveUI.SourceGenerators: Use source generators to generate objects.
Use source generators to generate objects. . Contribute to reactiveui/ReactiveUI.SourceGenerators development by creating an account on GitHub.
Micha-kun commented 3 months ago

No, that's a helper to implement IViewFor interface (and it's pretty good). I'm talking about specific controls that exists in ReactiveUI.Winforms NuGet like this:

https://github.com/reactiveui/ReactiveUI/blob/main/src/ReactiveUI.Winforms/ReactiveUserControl.cs

Those controls are used as base classes, and they force it's parent as (in this case) UserControl base class:

public partial class ReactiveUserControl<TViewModel> : **UserControl**, IViewFor<TViewModel> where TViewModel : class What would be incredible is if those base controls where generated via Source Generators and that UserControl parent reference be changed by our custom base control, so, in case of DevExpress, could use XtraUserControl and gain benefits of both functionalities.

GitHub
ReactiveUI/src/ReactiveUI.Winforms/ReactiveUserControl.cs at main · reactiveui/ReactiveUI
An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable st...
ChrisPulman commented 3 months ago

Technically the [IViewFor(nameof(MyViewModel))] Attribute will do exactly what you are wishing, it hasn't yet been released but we are working on getting our release mechanism ready to produce a release

using ReactiveUI.SourceGenerators;

namespace MyApp.WinForms;

[IViewFor(nameof(MyViewModel))]
public partial class MyReactiveUserControl : MyBaseUserControl
{
        /// <summary>
        /// Initializes a new instance of the <see cref="MyReactiveUserControl "/> class.
        /// </summary>
        public MyReactiveUserControl()
        {
            InitializeComponent();
            ViewModel = new MyViewModel();
        }
}

This will produce the following additional code which should maintain compatibility with the WinForms Designer.

using ReactiveUI;
using System.ComponentModel;

// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MyApp.WinForms
{
    [global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
    public partial class MyReactiveUserControl : IViewFor<MyViewModel>
    {
        /// <inheritdoc/>
        [Category("ReactiveUI")]
        [Description("The ViewModel.")]
        [Bindable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public MyViewModel? ViewModel { get; set; }

        /// <inheritdoc/>
        object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = (MyViewModel? )value; }
    }
}
#nullable restore
#pragma warning restore
Micha-kun commented 3 months ago

Well, in the case of ReactiveUserControl, this could be the replacement. But there are other specialized controls, like RoutedControlHost or ViewModelControlHost that are more than that:

https://github.com/reactiveui/ReactiveUI/blob/main/src/ReactiveUI.Winforms/RoutedViewHost.cs

https://github.com/reactiveui/ReactiveUI/blob/main/src/ReactiveUI.Winforms/ViewModelViewHost.cs

Those controls are the ones that currently could improve if we could change which base class is using (for example, instead of UserControl, use XtraUserControl).

Do you think is a bad idea?

GitHub
ReactiveUI/src/ReactiveUI.Winforms/RoutedViewHost.cs at main · reactiveui/ReactiveUI
An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable st...
GitHub
ReactiveUI/src/ReactiveUI.Winforms/ViewModelViewHost.cs at main · reactiveui/ReactiveUI
An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable st...
ChrisPulman commented 3 months ago

Okay this is a valid idea, I will try to put something together in the next few days to cover these too. All platforms could benefit from this too.

Micha-kun commented 3 months ago

Great to hear that! I was thinking it would require an specialized version of IViewFor attribute but implementing only the generic part of IViewFor, not the non-generic part, because the non-generic part would be on those controls and the property implementation would use the base ViewModel property from ReactiveUI specialized control. Something like this:

using ReactiveUI;
using System.ComponentModel;

// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MyApp.WinForms
{
    [global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
    public partial class MyReactiveUserControl : IViewFor<MyViewModel>
    {
        /// <inheritdoc/>
        [Category("ReactiveUI")]
        [Description("The ViewModel.")]
        [Bindable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public MyViewModel? ViewModel { get =>  (MyViewModel?)base.ViewModel; set => base.ViewModel = value; }
    }
}
#nullable restore
#pragma warning restore
Micha-kun commented 2 months ago

Won't be better to separate those specialized WinForms controls in a separate Nuget? Like ReactiveUI.SourceGenerators.WinForms. So each platform could have it's specialized nuget (Wpf, etc). They can require the "Core" one so everything is integrated.

ChrisPulman commented 2 months ago

As the code is generated in your project and you get no reference to the Source Generators in your final assembly I don't see the benefits of having separated packages, opted for the namespace difference to identify the functionality is for a particular platform. I need to look at the Wpf specialist functionality more carefully to work out the way to implement them properly.

ChrisPulman commented 2 months ago

V1.1.26 has been released, please try the WinForms Generators included

Micha-kun commented 2 months ago

Ok, after testing it's a great solution but I think it can be easier if it wouldn't require to pass as argument the base type. Base type can be applied directly in the "master class" as this:


[RoutedControlHost]
public partial class TestWinFormsRCHost : XtraUserControl
{
}

What do you think?