winforms icon indicating copy to clipboard operation
winforms copied to clipboard

Support generic base class in designer

Open nawfalhasan opened this issue 4 years ago • 15 comments

Is your feature request related to a problem? Please describe. Problem: Unable to open up a form that inherits from a generic form class without building project.

Description: When a form inherits from a common base class like FormBase<T>, the designer of the more derived type cant be opened without building the project in VS.

Example:

public class FormBase<T> : Form
{
    protected FormBase()
    {
    }
}

public class FormDerived : FormBase<DerivedViewModel>
{
    public FormDerived()
    {
    }
}

Now FormDerived in the designer wouldn't open up easily. Sometimes building VS is required to open up the designer, sometimes restarting VS and building is required.

Error when I try today: From the designer:

The designer could not be shown for this file because none of the classes within it can be designed. The designer inspected the following classes in the file: FormDerived --- The base class 'Xxx.Common.Presentation.Mvvm.FormBase`1' could not be loaded. Ensure the assembly has been referenced and that all projects have been built.

Call stack:

at System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.EnsureDocument(IDesignerSerializationManager manager) at System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager manager) at Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager serializationManager) --- End of stack trace from previous location where exception was thrown --- at Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager serializationManager) at System.ComponentModel.Design.Serialization.BasicDesignerLoader.BeginLoad(IDesignerLoaderHost host)

Solution I would like: Opening up the form designer should work any time from VS. Just like it opens up if base class is a regular class.

Related info:

  1. Here MS staff confirms this feature is on the roadmap. I am here trying to create a github issue for tracking. https://devblogs.microsoft.com/dotnet/windows-forms-designer-for-net-core-released/#comment-5898

  2. Similar thread before https://github.com/dotnet/roslyn/issues/4987

nawfalhasan avatar May 22 '20 08:05 nawfalhasan

This is a feature request, not an api-suggestion. Seems I cant add appropriate label.

nawfalhasan avatar May 22 '20 08:05 nawfalhasan

The same applies for user controls as well

nawfalhasan avatar May 22 '20 08:05 nawfalhasan

what type T would you expect the designer to display? the designer needs a live instance to run against, so it has to chose a generic type parameter. What would be the choice if the generic type has constraints? Would you expect it to be supported to have interface constraints and how would you pick the T for them?

public class FormBase<T> : Form where T: IDisposable, ISomeOtherInterface

weltkante avatar May 22 '20 09:05 weltkante

The designer for FormDerived : FormBase<DerivedViewModel> should resolve T as DerivedViewModel. If not, at the very least it should open up the designer of Form (i.e. first proper base it can design)

Designer need not verify generic constraints, but if it does that is fine as well (it would be more correct and straightforward for the compiler I guess). I am not sure what is the complexity here. The base here is bound and closed generic type.

nawfalhasan avatar May 22 '20 11:05 nawfalhasan

Designer need not verify generic constraints

The designer doesn't have a choice, the runtime does that when you instantiate an instance. You cannot create an instance of an invalid type or an open generic type (without specifying T). The designer doesn't contain a compiler, it just creates an instance off your last build and runs it.

The designer for FormDerived : FormBase<DerivedViewModel> should resolve T as DerivedViewModel.

Right, that works when your derived class is not generic anymore, I was also thinking of the case FormDerived<T> : FormBase<T> - maybe that doesn't need to be supported?

weltkante avatar May 22 '20 11:05 weltkante

I was also thinking of the case FormDerived<T> : FormBase<T> - maybe that doesn't need to be supported?

As a first step not. But it can be implemented too if MS let us specify a TypeDescriptionProvider (which controls which instance should designer design).

nawfalhasan avatar May 22 '20 11:05 nawfalhasan

This issue is still in the .NET Core designer. With .Net Framework it's fixed. Is there an ETA on this?

TimBakkerTooling avatar Sep 07 '22 20:09 TimBakkerTooling

I am afraid, no. The support for the generic/abstract forms/user controls is on the team's backlog, however, it's not very high on the priority list.

RussKie avatar Sep 08 '22 08:09 RussKie

Workaround that seems to work:

    public abstract class GenericForm<TViewModel> : Form
        where TViewModel : class
    {
        protected GenericForm()
        {
            InitializeComponent();
        }

        public GenericForm(TViewModel viewModel)
        {
            InitializeComponent();
            ViewModel = viewModel;
        }
        public TViewModel ViewModel { get; }

        private void InitializeComponent()
        {

        }
    }

    public class ViewModel
    {

    }

    public class NonGenericForm : GenericForm<ViewModel>
    {
        private NonGenericForm() : base()
        {

        }

        public NonGenericForm(ViewModel viewModel) : base(viewModel)
        {

        }
    }

    public class MainForm : NonGenericForm
    {
        public MainForm(ViewModel viewModel) : base(viewModel)
        {
            InitializeComponent();
        }
        private void InitializeComponent()
        {

        }
    }
}

(Same as with .NET Framework)

TimBakkerTooling avatar Sep 08 '22 08:09 TimBakkerTooling

Now that we have Generic Attributes in C# 11, we could specify the generic type arguments used by the designer in an attribute

[DesignerFormType<MyViewModel, MyDataModel>]
public class MyForm<TViewModel, TDataModel> : Form

Or with typeof() in older C# versions.

[DesignerFormType(typeof(MyViewModel), typeof(MyDataModel))]

OJacot-Descombes avatar Jan 04 '23 15:01 OJacot-Descombes

what type T would you expect the designer to display? the designer needs a live instance to run against, so it has to chose a generic type parameter. What would be the choice if the generic type has constraints? Would you expect it to be supported to have interface constraints and how would you pick the T for them?

public class FormBase<T> : Form where T: IDisposable, ISomeOtherInterface

It worked in .NET Framework, so how did you do it there? Even the generic base form could be opened as you might have controls on it.

IngoBleile avatar Sep 19 '23 11:09 IngoBleile

It worked in .NET Framework, so how did you do it there? Even the generic base form could be opened as you might have controls on it.

Nah, some scenarios worked, others worked sometimes, some never. The situation changed in the past too, when the compiler backend was changed to Roslyn, as mentioned and linked in the original post. It mostly depends on whether the designer is able to instantiate an instance. As far as I understand the other comments (including in the linked threads) generics were not a scenario the designer was coded for and the cases that worked only worked by chance.

Overall I'm the wrong person to argue with anyways, my response you quoted was just trying to explain why these things are problematic. It doesn't really matter what the old designer did, as the new designer is completely different (a rewrite not a port) and a lot of things that used to work no longer work or work differently. If you want something to work that currently doesn't you have to ask the team to support it and if enough people do maybe they will.

weltkante avatar Sep 19 '23 18:09 weltkante

In my case, the generic type argument would have no bearing on the designer rendering. I use it for other concerns. Why not construct the UserControl<T> with default(T)?

Another option would be to give us a place to enrich the design processes to do explicit construction. Perhaps an interface (IWinformsDesignerInfo) that we can implement that your library can find and construct with reflection. Give us a method to implement like one of the following: UserControl GetUserControlInstance(Type type) TControl GetUserControlInstance<TControl>()

This way we can provide the constructed object of the needed type.

n-ate avatar May 26 '24 02:05 n-ate

Why not construct the UserControl with default(T)?

Because MyUserControlBaseClass<default<T>> is not a valid type signature - you need to put in a Type for T not a value. But yes, it'd be "relatively easy" to provide a workaround for some scenarios in the new designer implementation if they desired to support that scenario.

weltkante avatar May 26 '24 14:05 weltkante

In my case, I am inheriting from a base User controller that exposes helpful methods and a "Model" property. The inheriting user controllers specify the type argument when inheriting which results in Model being the correct property and all of the inherited methods taking the correct types.

n-ate avatar Jun 06 '24 19:06 n-ate